]> git.kaiwu.me - njs.git/commitdiff
Cap string-producing chb chains in core builtins
authorDmitry Volyntsev <xeioex@nginx.com>
Tue, 21 Apr 2026 04:32:19 +0000 (21:32 -0700)
committerDmitry Volyntsev <xeioexception@gmail.com>
Wed, 10 Jun 2026 20:38:58 +0000 (13:38 -0700)
Overflow now trips during chain growth and surfaces as a catchable
RangeError("invalid string length") instead of continuing to inflate
the VM memory pool until the process OOMs.

Out of scope (non-JS-string chains):
  - njs_vm.c AST serialization
  - njs_json.c njs_vm_value_dump (console.log value dump)
  - njs_function.c new Function() source assembly
  - njs_error.c error message composition
  - external/ modules on both engines (querystring, xml, zlib)

This fixes OOM in staging/sm/String/replace-math Tests262.

src/njs_array.c
src/njs_json.c
src/njs_regexp.c
src/njs_string.c
src/njs_typed_array.c
src/test/njs_unit_test.c

index eb700aa342c4ee8f02c853676b8c13d53c374780..e91e6b22ef1f1ca4fb71ae36bf7bb122df42ae63 100644 (file)
@@ -1695,7 +1695,7 @@ njs_array_prototype_join(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
 
     value = &entry;
 
-    NJS_CHB_MP_INIT(&chain, njs_vm_memory_pool(vm));
+    NJS_CHB_MP_INIT_MAX(&chain, njs_vm_memory_pool(vm), NJS_STRING_MAX_LENGTH);
 
     for (i = 0; i < len; i++) {
         ret = njs_value_property_i64(vm, this, i, value);
@@ -1732,7 +1732,13 @@ njs_array_prototype_join(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
 
     size = njs_chb_size(&chain);
     if (njs_slow_path(size < 0)) {
-        njs_memory_error(vm);
+        if (chain.error == NJS_CHB_ERR_OVERFLOW) {
+            njs_range_error(vm, "invalid string length");
+
+        } else {
+            njs_memory_error(vm);
+        }
+
         return NJS_ERROR;
     }
 
index 166fb1b1781680ea3eaf701c665d1e6148b6b00e..e6a849a81a0cd9293e17d811e07aeae32d77596f 100644 (file)
@@ -1081,7 +1081,8 @@ njs_json_stringify_iterator(njs_json_stringify_t *stringify,
         goto memory_error;
     }
 
-    NJS_CHB_MP_INIT(&chain, njs_vm_memory_pool(stringify->vm));
+    NJS_CHB_MP_INIT_MAX(&chain, njs_vm_memory_pool(stringify->vm),
+                        NJS_STRING_MAX_LENGTH);
 
     for ( ;; ) {
         if (state->index == 0) {
@@ -1188,8 +1189,16 @@ done:
 
     size = njs_chb_size(&chain);
     if (njs_slow_path(size < 0)) {
+        if (chain.error == NJS_CHB_ERR_OVERFLOW) {
+            njs_range_error(stringify->vm, "invalid string length");
+
+        } else {
+            njs_memory_error(stringify->vm);
+        }
+
         njs_chb_destroy(&chain);
-        goto memory_error;
+
+        return NJS_ERROR;
     }
 
     if (size == 0) {
@@ -1200,7 +1209,7 @@ done:
     ret = njs_string_create_chb(stringify->vm, retval, &chain);
     if (njs_slow_path(ret != NJS_OK)) {
         njs_chb_destroy(&chain);
-        goto memory_error;
+        return NJS_ERROR;
     }
 
 release:
index 7d09d5c8e5a3b70d901d2181e85e87c76a092fe9..6ab21123937b576d15702d99ca7ff6dbbf3e5e8f 100644 (file)
@@ -1321,7 +1321,7 @@ njs_regexp_prototype_symbol_replace(njs_vm_t *vm, njs_value_t *args,
         }
     }
 
-    NJS_CHB_MP_INIT(&chain, njs_vm_memory_pool(vm));
+    NJS_CHB_MP_INIT_MAX(&chain, njs_vm_memory_pool(vm), NJS_STRING_MAX_LENGTH);
 
     results.separate = 0;
     results.pointer = 0;
index d49f58720705785428ad6a741bff0fa89d8e6e6d..09b515ba0de6750cce591ea35178cad4d7912bf9 100644 (file)
@@ -2958,7 +2958,7 @@ njs_string_get_substitution(njs_vm_t *vm, njs_value_t *matched,
     p = rep.start;
     end = rep.start + rep.length;
 
-    NJS_CHB_MP_INIT(&chain, njs_vm_memory_pool(vm));
+    NJS_CHB_MP_INIT_MAX(&chain, njs_vm_memory_pool(vm), NJS_STRING_MAX_LENGTH);
 
     while (p < end) {
         r = njs_strlchr(p, end, '$');
@@ -3242,7 +3242,7 @@ njs_string_prototype_replace(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
         return NJS_OK;
     }
 
-    NJS_CHB_MP_INIT(&chain, njs_vm_memory_pool(vm));
+    NJS_CHB_MP_INIT_MAX(&chain, njs_vm_memory_pool(vm), NJS_STRING_MAX_LENGTH);
 
     start = string.start;
 
@@ -3826,6 +3826,11 @@ njs_string_decode_uri(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     src = string.start;
     end = string.start + string.size;
 
+    /*
+     * Percent-decoding never produces more bytes than it consumes and
+     * the input is a string within NJS_STRING_MAX_LENGTH, so the chain
+     * needs no byte cap.
+     */
     NJS_CHB_MP_INIT(&chain, njs_vm_memory_pool(vm));
 
     njs_utf8_decode_init(&ctx);
@@ -4100,6 +4105,11 @@ njs_string_atob(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
 
     len = njs_base64_decoded_length(len, pad);
 
+    /*
+     * The chain holds a single reservation of at most twice the decoded
+     * length, which is bounded by the input string size, so it cannot
+     * grow unbounded and needs no byte cap.
+     */
     NJS_CHB_MP_INIT(&chain, njs_vm_memory_pool(vm));
 
     dst = njs_chb_reserve(&chain, len * 2);
index d007285ad86a7a5a8ac69704e11ca3178d4d63bd..508331bc065ee0e450fba446b83aca701acc4828 100644 (file)
@@ -2141,7 +2141,7 @@ njs_typed_array_prototype_join(njs_vm_t *vm, njs_value_t *args,
         return NJS_ERROR;
     }
 
-    NJS_CHB_MP_INIT(&chain, njs_vm_memory_pool(vm));
+    NJS_CHB_MP_INIT_MAX(&chain, njs_vm_memory_pool(vm), NJS_STRING_MAX_LENGTH);
 
     njs_typed_array_to_chain(vm, &chain, array, separator);
 
index 92635708d4130fb6f617b2c316058a9ad8dd5b0f..26c894342066321e714ef1a4d61e3f5aac86e66a 100644 (file)
@@ -8721,6 +8721,18 @@ static njs_unit_test_t  njs_test[] =
                  "String.prototype.concat.apply(s, a.slice(1))"),
       njs_str("RangeError: invalid string length") },
 
+#if (NJS_64BIT)
+    /*
+     * Adversarial regex replace must throw RangeError instead of
+     * OOM-killing the process.  The chain accumulates ~2GiB before the
+     * cap trips, hence 64-bit only.
+     */
+    { njs_str("var s = 'x'.repeat(1 << 18);"
+                 "var r = '$1'.repeat(1 << 14);"
+                 "s.replace(/(.+)/g, r)"),
+      njs_str("RangeError: invalid string length") },
+#endif
+
     { njs_str("var a = 'abcdefgh'; a.substr(3, 15)"),
       njs_str("defgh") },