[njs] Fixed heap-buffer-overflow for RegExp.prototype[Symbol.replace].

Alexander Borisov alexander.borisov at nginx.com
Tue Oct 13 12:45:23 UTC 2020


details:   https://hg.nginx.org/njs/rev/a82f123409b7
branches:  
changeset: 1539:a82f123409b7
user:      Alexander Borisov <alexander.borisov at nginx.com>
date:      Tue Oct 13 15:44:33 2020 +0300
description:
Fixed heap-buffer-overflow for RegExp.prototype[Symbol.replace].

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.

diffstat:

 src/njs_regexp.c         |  14 ++++++++------
 src/test/njs_unit_test.c |  12 ++++++++++++
 2 files changed, 20 insertions(+), 6 deletions(-)

diffs (72 lines):

diff -r 893fa730285c -r a82f123409b7 src/njs_regexp.c
--- a/src/njs_regexp.c	Thu Oct 08 18:47:04 2020 +0300
+++ b/src/njs_regexp.c	Tue Oct 13 15:44:33 2020 +0300
@@ -1343,14 +1343,14 @@ njs_regexp_prototype_symbol_replace(njs_
 
         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_
                 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_
             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_
             }
 
             ret = njs_string_get_substitution(vm, &matched, string, pos,
-                                              arguments, ncaptures - 1, &groups,
+                                              arguments, ncaptures, &groups,
                                               replace, &retval);
 
         } else {
diff -r 893fa730285c -r a82f123409b7 src/test/njs_unit_test.c
--- a/src/test/njs_unit_test.c	Thu Oct 08 18:47:04 2020 +0300
+++ b/src/test/njs_unit_test.c	Tue Oct 13 15:44:33 2020 +0300
@@ -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("βγαβγ") },
 


More information about the nginx-devel mailing list