CVE-2022-43286 (published 2022-10-28T21:15Z)

Nginx NJS v0.7.2 was discovered to contain a heap-use-after-free bug caused by illegal memory copy in the function njs_json_parse_iterator_call at njs_json.c.

CVSS v3 base score: 9.8, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-416
cwe_name: Use After Free
description: The product reuses or references memory after it has been freed. At some point afterward, the memory may be allocated again and saved in another pointer, while the original pointer references a location somewhere within the new allocation. Any operations using the original pointer are no longer valid because the memory "belongs" to the code that operates on the new pointer.
extended_description: 
             
             
             
			 
             
             
         
         

Fix information:
repo_url: https://github.com/nginx/njs
commit_details: repo_url: https://github.com/nginx/njs
author: Dmitry Volyntsev
author_date: 2022-05-04 16:44:48-07:00
msg: Fixed JSON.parse() when reviver function is provided.

This closes #480 issue on Github.
repository: repo_url: https://github.com/nginx/njs
repo_name: nginx/njs
description: A subset of JavaScript language to use in nginx
repo_language: C
owner: nginx
file_changes: filename: njs_json.c
change_type: ModificationType.MODIFY
diff: @@ -28,27 +28,17 @@ typedef struct {
     int64_t                    length;
     njs_array_t                *keys;
     njs_value_t                *key;
-    njs_object_prop_t          *prop;
+    njs_value_t                prop;
 } njs_json_state_t;
 
 
-typedef struct {
-    njs_value_t                retval;
-
-    njs_uint_t                 depth;
-#define NJS_JSON_MAX_DEPTH     32
-    njs_json_state_t           states[NJS_JSON_MAX_DEPTH];
-
-    njs_function_t             *function;
-} njs_json_parse_t;
-
-
 typedef struct {
     njs_value_t                retval;
 
     njs_vm_t                   *vm;
 
     njs_uint_t                 depth;
+#define NJS_JSON_MAX_DEPTH     32
     njs_json_state_t           states[NJS_JSON_MAX_DEPTH];
 
     njs_value_t                replacer;
@@ -72,10 +62,9 @@ njs_inline uint32_t njs_json_unicode(const u_char *p);
 static const u_char *njs_json_skip_space(const u_char *start,
     const u_char *end);
 
-static njs_int_t njs_json_parse_iterator(njs_vm_t *vm, njs_json_parse_t *parse,
-    njs_value_t *value);
-static njs_int_t njs_json_parse_iterator_call(njs_vm_t *vm,
-    njs_json_parse_t *parse, njs_json_state_t *state);
+static njs_int_t njs_json_internalize_property(njs_vm_t *vm,
+    njs_function_t *reviver, njs_value_t *holder, njs_value_t *name,
+    njs_int_t depth, njs_value_t *retval);
 static void njs_json_parse_exception(njs_json_parse_ctx_t *ctx,
     const char *msg, const u_char *pos);
 
@@ -108,15 +97,13 @@ njs_json_parse(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     njs_index_t unused)
 {
     njs_int_t             ret;
-    njs_value_t           *text, value, lvalue;
+    njs_value_t           *text, value, lvalue, wrapper;
+    njs_object_t          *obj;
     const u_char          *p, *end;
-    njs_json_parse_t      *parse, json_parse;
     const njs_value_t     *reviver;
     njs_string_prop_t     string;
     njs_json_parse_ctx_t  ctx;
 
-    parse = &json_parse;
-
     text = njs_lvalue_arg(&lvalue, args, nargs, 1);
 
     if (njs_slow_path(!njs_is_string(text))) {
@@ -156,11 +143,16 @@ njs_json_parse(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
 
     reviver = njs_arg(args, nargs, 2);
 
-    if (njs_slow_path(njs_is_function(reviver) && njs_is_object(&value))) {
-        parse->function = njs_function(reviver);
-        parse->depth = 0;
+    if (njs_slow_path(njs_is_function(reviver))) {
+        obj = njs_json_wrap_value(vm, &wrapper, &value);
+        if (njs_slow_path(obj == NULL)) {
+            return NJS_ERROR;
+        }
 
-        return njs_json_parse_iterator(vm, parse, &value);
+        return njs_json_internalize_property(vm, njs_function(reviver),
+                                             &wrapper,
+                                             njs_value_arg(&njs_string_empty),
+                                             0, &vm->retval);
     }
 
     vm->retval = value;
@@ -851,195 +843,106 @@ njs_json_skip_space(const u_char *start, const u_char *end)
 }
 
 
-static njs_json_state_t *
-njs_json_push_parse_state(njs_vm_t *vm, njs_json_parse_t *parse,
-    njs_value_t *value)
-{
-    njs_json_state_t  *state;
-
-    if (njs_slow_path(parse->depth >= NJS_JSON_MAX_DEPTH)) {
-        njs_type_error(vm, "Nested too deep or a cyclic structure");
-        return NULL;
-    }
-
-    state = &parse->states[parse->depth++];
-    state->value = *value;
-    state->index = 0;
-    state->prop = NULL;
-    state->keys = njs_value_own_enumerate(vm, value, NJS_ENUM_KEYS,
-                                          NJS_ENUM_STRING, 0);
-    if (state->keys == NULL) {
-        return NULL;
-    }
-
-    return state;
-}
-
-
-njs_inline njs_json_state_t *
-njs_json_pop_parse_state(njs_vm_t *vm, njs_json_parse_t *parse)
-{
-    njs_json_state_t  *state;
-
-    state = &parse->states[parse->depth - 1];
-    njs_array_destroy(vm, state->keys);
-    state->keys = NULL;
-
-    if (parse->depth > 1) {
-        parse->depth--;
-        return &parse->states[parse->depth - 1];
-    }
-
-    return NULL;
-}
-
-
 static njs_int_t
-njs_json_parse_iterator(njs_vm_t *vm, njs_json_parse_t *parse,
-    njs_value_t *object)
+njs_json_internalize_property(njs_vm_t *vm, njs_function_t *reviver,
+    njs_value_t *holder, njs_value_t *name, njs_int_t depth,
+    njs_value_t *retval)
 {
-    njs_int_t             ret;
-    njs_value_t           *key, wrapper;
-    njs_object_t          *obj;
-    njs_json_state_t      *state;
-    njs_object_prop_t     *prop;
-    njs_property_query_t  pq;
+    int64_t       k, length;
+    njs_int_t     ret;
+    njs_value_t   val, new_elem, index;
+    njs_value_t   arguments[3];
+    njs_array_t   *keys;
 
-    obj = njs_json_wrap_value(vm, &wrapper, object);
-    if (njs_slow_path(obj == NULL)) {
+    if (njs_slow_path(depth++ >= NJS_JSON_MAX_DEPTH)) {
+        njs_type_error(vm, "Nested too deep or a cyclic structure");
         return NJS_ERROR;
     }
 
-    state = njs_json_push_parse_state(vm, parse, &wrapper);
-    if (njs_slow_path(state == NULL)) {
+    ret = njs_value_property(vm, holder, name, &val);
+    if (njs_slow_path(ret == NJS_ERROR)) {
         return NJS_ERROR;
     }
 
-    for ( ;; ) {
-        if (state->index < state->keys->length) {
-            njs_property_query_init(&pq, NJS_PROPERTY_QUERY_SET, 0);
-
-            key = &state->keys->start[state->index];
-
-            ret = njs_property_query(vm, &pq, &state->value, key);
-            if (njs_slow_path(ret != NJS_OK)) {
-                if (ret == NJS_DECLINED) {
-                    state->index++;
-                    continue;
-                }
+    keys = NULL;
 
+    if (njs_is_object(&val)) {
+        if (!njs_is_array(&val)) {
+            keys = njs_array_keys(vm, &val, 0);
+            if (njs_slow_path(keys == NULL)) {
                 return NJS_ERROR;
             }
 
-            prop = pq.lhq.value;
-
-            if (prop->type == NJS_WHITEOUT) {
-                state->index++;
-                continue;
-            }
-
-            state->prop = prop;
+            for (k = 0; k < keys->length; k++) {
+                ret = njs_json_internalize_property(vm, reviver, &val,
+                                                    &keys->start[k], depth,
+                                                    &new_elem);
 
-            if (prop->type == NJS_PROPERTY && njs_is_object(&prop->value)) {
-                state = njs_json_push_parse_state(vm, parse, &prop->value);
-                if (state == NULL) {
-                    return NJS_ERROR;
+                if (njs_slow_path(ret != NJS_OK)) {
+                    goto done;
                 }
 
-                continue;
-            }
+                if (njs_is_undefined(&new_elem)) {
+                    ret = njs_value_property_delete(vm, &val, &keys->start[k],
+                                                    NULL, 0);
 
-            if (prop->type == NJS_PROPERTY_REF
-                && njs_is_object(prop->value.data.u.value))
-            {
-                state = njs_json_push_parse_state(vm, parse,
-                                                  prop->value.data.u.value);
-                if (state == NULL) {
-                    return NJS_ERROR;
+                } else {
+                    ret = njs_value_property_set(vm, &val, &keys->start[k],
+                                                 &new_elem);
                 }
 
-                continue;
+                if (njs_slow_path(ret == NJS_ERROR)) {
+                    goto done;
+                }
             }
 
         } else {
-            state = njs_json_pop_parse_state(vm, parse);
-            if (state == NULL) {
-                vm->retval = parse->retval;
-                return NJS_OK;
-            }
-        }
-
-        ret = njs_json_parse_iterator_call(vm, parse, state);
-        if (njs_slow_path(ret != NJS_OK)) {
-            return ret;
-        }
-    }
-}
-
 
-static njs_int_t
-njs_json_parse_iterator_call(njs_vm_t *vm, njs_json_parse_t *parse,
-    njs_json_state_t *state)
-{
-    njs_int_t          ret;
-    njs_value_t        arguments[3], *value;
-    njs_object_prop_t  *prop;
-
-    prop = state->prop;
-
-    arguments[0] = state->value;
-    arguments[1] = state->keys->start[state->index++];
-
-    switch (prop->type) {
-    case NJS_PROPERTY:
-        arguments[2] = prop->value;
+            ret = njs_object_length(vm, &val, &length);
+            if (njs_slow_path(ret == NJS_ERROR)) {
+                return NJS_ERROR;
+            }
 
-        ret = njs_function_apply(vm, parse->function, arguments, 3,
-                                 &parse->retval);
-        if (njs_slow_path(ret != NJS_OK)) {
-            return ret;
-        }
+            for (k = 0; k < length; k++) {
+                ret = njs_int64_to_string(vm, &index, k);
+                if (njs_slow_path(ret != NJS_OK)) {
+                    return NJS_ERROR;
+                }
 
-        if (njs_is_undefined(&parse->retval)) {
-            prop->type = NJS_WHITEOUT;
+                ret = njs_json_internalize_property(vm, reviver, &val, &index,
+                                                    depth, &new_elem);
 
-        } else {
-            prop->value = parse->retval;
-        }
+                if (njs_slow_path(ret != NJS_OK)) {
+                    return NJS_ERROR;
+                }
 
-        break;
+                if (njs_is_undefined(&new_elem)) {
+                    ret = njs_value_property_delete(vm, &val, &index, NULL, 0);
 
-    case NJS_PROPERTY_REF:
-        value = prop->value.data.u.value;
-        arguments[2] = *value;
+                } else {
+                    ret = njs_value_property_set(vm, &val, &index, &new_elem);
+                }
 
-        ret = njs_function_apply(vm, parse->function, arguments, 3,
-                                 &parse->retval);
-        if (njs_slow_path(ret != NJS_OK)) {
-            return ret;
+                if (njs_slow_path(ret == NJS_ERROR)) {
+                    return NJS_ERROR;
+                }
+            }
         }
+    }
 
-        if (njs_is_undefined(&parse->retval)) {
-            ret = njs_value_property_i64_delete(vm, &state->value,
-                                                state->index - 1, NULL);
-
-        } else {
-            ret = njs_value_property_i64_set(vm, &state->value,
-                                             state->index - 1, &parse->retval);
-        }
+    njs_value_assign(&arguments[0], holder);
+    njs_value_assign(&arguments[1], name);
+    njs_value_assign(&arguments[2], &val);
 
-        if (njs_slow_path(ret == NJS_ERROR)) {
-            return NJS_ERROR;
-        }
+    ret = njs_function_apply(vm, reviver, arguments, 3, retval);
 
-        break;
+done:
 
-    default:
-        njs_internal_error(vm, "njs_json_parse_iterator_call() unexpected "
-                         "property type:%s", njs_prop_type_string(prop->type));
+    if (keys != NULL) {
+        njs_array_destroy(vm, keys);
     }
 
-    return NJS_OK;
+    return ret;
 }
 
 

programming_language: C++
filename: njs_benchmark.c
change_type: ModificationType.MODIFY
diff: @@ -208,6 +208,16 @@ static njs_benchmark_test_t  njs_test[] =
       njs_str("123"),
       1000000 },
 
+    { "JSON.parse large",
+      njs_str("JSON.parse(JSON.stringify([Array(2**16)]))[0].length"),
+      njs_str("65536"),
+      10 },
+
+    { "JSON.parse reviver large",
+      njs_str("JSON.parse(JSON.stringify([Array(2**16)]), v=>v)"),
+      njs_str(""),
+      10 },
+
     { "for loop 100M",
       njs_str("var i; for (i = 0; i < 100000000; i++); i"),
       njs_str("100000000"),

programming_language: C
filename: njs_unit_test.c
change_type: ModificationType.MODIFY
diff: @@ -17203,6 +17203,31 @@ static njs_unit_test_t  njs_test[] =
               "JSON.parse('[1]', func);"),
       njs_str("") },
 
+    { njs_str("JSON.parse(JSON.stringify([Array(2**16)]), v => v)"),
+      njs_str("") },
+
+    { njs_str("var order = []; function reviver(k, v) { order.push(k); };"
+              "JSON.parse('{\"p1\":0,\"p2\":0,\"p1\":0,\"2\":0,\"1\":0}', reviver);"
+              "order"),
+      njs_str("1,2,p1,p2,") },
+
+    { njs_str("function reviver(k, v) {"
+              "    if (k == '0') Object.defineProperty(this, '1', {configurable: false});"
+              "    if (k == '1') return;"
+              "    return v;"
+              " };"
+              "JSON.parse('[1, 2]', reviver)"),
+      njs_str("1,2") },
+
+    { njs_str("JSON.parse('0', (k, v) => {throw 'Oops'})"),
+      njs_str("Oops") },
+
+    { njs_str("JSON.parse('{\"a\":1}', (k, v) => {if (k == 'a') {throw 'Oops'}; return v;})"),
+      njs_str("Oops") },
+
+    { njs_str("JSON.parse('[2,3,43]', (k, v) => {if (v == 43) {throw 'Oops'}; return v;})"),
+      njs_str("Oops") },
+
     /* JSON.stringify() */
 
     { njs_str("JSON.stringify()"),

programming_language: JavaScript