[njs] Fixed Array.prototype.join() when array is changed while iterating.

Dmitry Volyntsev xeioex at nginx.com
Wed Jan 12 18:00:11 UTC 2022


details:   https://hg.nginx.org/njs/rev/9578cc729205
branches:  
changeset: 1802:9578cc729205
user:      Dmitry Volyntsev <xeioex at nginx.com>
date:      Wed Jan 12 17:59:42 2022 +0000
description:
Fixed Array.prototype.join() when array is changed while iterating.

Previously, the function used optimization for ordinary arrays with no
gaps (so called fast arrays).  For a fast array code took elements
directly from internal flat C array. The direct pointer may become
invalid as side-effect of custom toString() method for an element.

Specifically, the pointer was passed directly to
njs_value_to_primitive() which attempts to call toString() followed by
valueOf().  When the array size is changed as a side-effect of
toString() and not a string value is returned by toString() the pointer
becomes invalid and is passed to valueOf() which causes use-after-free.

The fix is to eliminate the micro-optimization which uses direct pointers.

Found by PolyGlot fuzzing framework.

This closes #444 issue on Github.

diffstat:

 src/njs_array.c          |  34 +++++++++-------------------------
 src/test/njs_unit_test.c |   8 ++++++++
 2 files changed, 17 insertions(+), 25 deletions(-)

diffs (83 lines):

diff -r 2adc0d3fc2bd -r 9578cc729205 src/njs_array.c
--- a/src/njs_array.c	Wed Jan 12 17:58:19 2022 +0000
+++ b/src/njs_array.c	Wed Jan 12 17:59:42 2022 +0000
@@ -1573,7 +1573,6 @@ njs_array_prototype_join(njs_vm_t *vm, n
     njs_int_t          ret;
     njs_chb_t          chain;
     njs_utf8_t         utf8;
-    njs_array_t        *array;
     njs_value_t        *value, *this, entry;
     njs_string_prop_t  separator, string;
 
@@ -1606,18 +1605,11 @@ njs_array_prototype_join(njs_vm_t *vm, n
     }
 
     length = 0;
-    array = NULL;
     utf8 = njs_is_byte_string(&separator) ? NJS_STRING_BYTE : NJS_STRING_UTF8;
 
-    if (njs_is_fast_array(this)) {
-        array = njs_array(this);
-        len = array->length;
-
-    } else {
-        ret = njs_object_length(vm, this, &len);
-        if (njs_slow_path(ret == NJS_ERROR)) {
-            return ret;
-        }
+    ret = njs_object_length(vm, this, &len);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        return ret;
     }
 
     if (njs_slow_path(len == 0)) {
@@ -1625,25 +1617,17 @@ njs_array_prototype_join(njs_vm_t *vm, n
         return NJS_OK;
     }
 
+    value = &entry;
+
     njs_chb_init(&chain, vm->mem_pool);
 
     for (i = 0; i < len; i++) {
-        if (njs_fast_path(array != NULL
-                          && array->object.fast_array
-                          && njs_is_valid(&array->start[i])))
-        {
-            value = &array->start[i];
-
-        } else {
-            ret = njs_value_property_i64(vm, this, i, &entry);
-            if (njs_slow_path(ret == NJS_ERROR)) {
-                return ret;
-            }
-
-            value = &entry;
+        ret = njs_value_property_i64(vm, this, i, value);
+        if (njs_slow_path(ret == NJS_ERROR)) {
+            return ret;
         }
 
-        if (njs_is_valid(value) && !njs_is_null_or_undefined(value)) {
+        if (!njs_is_null_or_undefined(value)) {
             if (!njs_is_string(value)) {
                 last = njs_chb_current(&chain);
 
diff -r 2adc0d3fc2bd -r 9578cc729205 src/test/njs_unit_test.c
--- a/src/test/njs_unit_test.c	Wed Jan 12 17:58:19 2022 +0000
+++ b/src/test/njs_unit_test.c	Wed Jan 12 17:59:42 2022 +0000
@@ -4801,6 +4801,14 @@ static njs_unit_test_t  njs_test[] =
               ".map(v=>v.join(''))"),
       njs_str(",1345,,1,13,13,13") },
 
+    { njs_str("var o = { toString: () => {"
+              "             for (var i = 0; i < 0x10; i++) {a.push(1)};"
+              "             return {};"
+              "}};"
+              "var a = [o];"
+              "a.join()"),
+      njs_str("TypeError: Cannot convert object to primitive value") },
+
     { njs_str("Array.prototype.splice.call({0:0,1:1,2:2,3:3,length:4},0,3,4,5)"),
       njs_str("0,1,2") },
 



More information about the nginx-devel mailing list