]> git.kaiwu.me - njs.git/commitdiff
Fixed heap-buffer-overflow for RegExp.prototype[Symbol.replace].
authorAlexander Borisov <alexander.borisov@nginx.com>
Tue, 13 Oct 2020 12:44:33 +0000 (15:44 +0300)
committerAlexander Borisov <alexander.borisov@nginx.com>
Tue, 13 Oct 2020 12:44:33 +0000 (15:44 +0300)
Previously, RegExp.prototype[Symbol.replace] might overrun the boundaries
of the result of the custom "exec" method for a RegExp argument. The
issue occurred when the result object had zero length.  The length is
used to create an array and the zero index was always written without
respect for the length resulting is heap-buffer-overflow.

The issue was introduced in 1c729f765cfb.

src/njs_regexp.c
src/test/njs_unit_test.c

index ed9a11458bf42f70e2362ca345f3d2d2d123c0b8..81dd2e5cc64eae86991b266311a248bf431d25e6 100644 (file)
@@ -1343,14 +1343,14 @@ njs_regexp_prototype_symbol_replace(njs_vm_t *vm, njs_value_t *args,
 
         pos = njs_max(njs_min(pos, (int64_t) s.size), 0);
 
-        if (njs_fast_path(njs_is_fast_array(r))) {
+        if (njs_fast_path(njs_is_fast_array(r) && njs_array_len(r) != 0)) {
             array = njs_array(r);
 
             arguments = array->start;
             arguments[0] = matched;
-            ncaptures = array->length;
+            ncaptures = njs_max((int64_t) array->length - 1, 0);
 
-            for (n = 1; n < ncaptures; n++) {
+            for (n = 1; n <= ncaptures; n++) {
                 if (njs_is_undefined(&arguments[n])) {
                     continue;
                 }
@@ -1367,7 +1367,9 @@ njs_regexp_prototype_symbol_replace(njs_vm_t *vm, njs_value_t *args,
                 goto exception;
             }
 
-            array = njs_array_alloc(vm, 0, ncaptures, 0);
+            ncaptures = njs_max(ncaptures - 1, 0);
+
+            array = njs_array_alloc(vm, 0, ncaptures + 1, 0);
             if (njs_slow_path(array == NULL)) {
                 goto exception;
             }
@@ -1375,7 +1377,7 @@ njs_regexp_prototype_symbol_replace(njs_vm_t *vm, njs_value_t *args,
             arguments = array->start;
             arguments[0] = matched;
 
-            for (n = 1; n < ncaptures; n++) {
+            for (n = 1; n <= ncaptures; n++) {
                 ret = njs_value_property_i64(vm, r, n, &arguments[n]);
                 if (njs_slow_path(ret == NJS_ERROR)) {
                     goto exception;
@@ -1406,7 +1408,7 @@ njs_regexp_prototype_symbol_replace(njs_vm_t *vm, njs_value_t *args,
             }
 
             ret = njs_string_get_substitution(vm, &matched, string, pos,
-                                              arguments, ncaptures - 1, &groups,
+                                              arguments, ncaptures, &groups,
                                               replace, &retval);
 
         } else {
index f79e2b9ed65736c47eb4c6de350c1df50f53b3df..435fb2adf552458cfa82ad0e2cc45f0ea7a9552b 100644 (file)
@@ -8284,6 +8284,18 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("/b(c)(z)?(.)/[Symbol.replace]('abcde', '[$01$02$03$04$00]')"),
       njs_str("a[cd$04$00]e") },
 
+    { njs_str("var r = /./; r.exec = () => {return {}};"
+              "r[Symbol.replace]('ABCD', 'b')"),
+      njs_str("b") },
+
+    { njs_str("var r = /./; r.exec = () => {return {}};"
+              "r[Symbol.replace]('ABCD', (m,p,o) => `${m}|${p}|${o}`)"),
+      njs_str("undefined|0|ABCD") },
+
+    { njs_str("var r = /./; r.exec = () => Buffer.from([]).toJSON().data;"
+              "r[Symbol.replace]('ABCD', 'b')"),
+      njs_str("b") },
+
     { njs_str("'α'.replace(/(h*)/g, '$1βγ')"),
       njs_str("βγαβγ") },