changeset 2563:742e5ffd8f81

Fixed Function constructor template injection. The Function constructor uses a `(function(<args>) {<body>})` template to construct new functions. This approach was vulnerable to template injection where malicious code could close the function body early. This fixes issue #921.
author Dmitry Volyntsev <xeioex@nginx.com>
date Mon, 02 Jun 2025 17:18:22 -0700
parents f114be6952f9
children 63438e4b702b
files src/njs_function.c src/test/njs_unit_test.c
diffstat 2 files changed, 28 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/src/njs_function.c	Wed May 28 09:16:38 2025 -0700
+++ b/src/njs_function.c	Mon Jun 02 17:18:22 2025 -0700
@@ -1049,7 +1049,7 @@
         }
     }
 
-    njs_chb_append_literal(&chain, "){");
+    njs_chb_append_literal(&chain, "\n){\n");
 
     if (nargs > 1) {
         ret = njs_value_to_chain(vm, &chain, njs_argument(args, nargs - 1));
@@ -1058,7 +1058,7 @@
         }
     }
 
-    njs_chb_append_literal(&chain, "})");
+    njs_chb_append_literal(&chain, "\n})");
 
     ret = njs_chb_join(&chain, &str);
     if (njs_slow_path(ret != NJS_OK)) {
@@ -1125,7 +1125,15 @@
 
     njs_chb_destroy(&chain);
 
-    lambda = ((njs_vmcode_function_t *) generator.code_start)->lambda;
+    if ((code->end - code->start)
+        != (sizeof(njs_vmcode_function_t) + sizeof(njs_vmcode_return_t))
+        || ((njs_vmcode_generic_t *) code->start)->code != NJS_VMCODE_FUNCTION)
+    {
+        njs_syntax_error(vm, "single function literal required");
+        return NJS_ERROR;
+    }
+
+    lambda = ((njs_vmcode_function_t *) code->start)->lambda;
 
     function = njs_function_alloc(vm, lambda, (njs_bool_t) async);
     if (njs_slow_path(function == NULL)) {
--- a/src/test/njs_unit_test.c	Wed May 28 09:16:38 2025 -0700
+++ b/src/test/njs_unit_test.c	Mon Jun 02 17:18:22 2025 -0700
@@ -14189,22 +14189,22 @@
       njs_str("true") },
 
     { njs_str("new Function('('.repeat(2**13));"),
-      njs_str("SyntaxError: Unexpected token \"}\" in runtime:1") },
+      njs_str("SyntaxError: Unexpected token \"}\" in runtime") },
 
     { njs_str("new Function('{'.repeat(2**13));"),
-      njs_str("SyntaxError: Unexpected token \")\" in runtime:1") },
+      njs_str("SyntaxError: Unexpected token \")\" in runtime") },
 
     { njs_str("new Function('['.repeat(2**13));"),
-      njs_str("SyntaxError: Unexpected token \"}\" in runtime:1") },
+      njs_str("SyntaxError: Unexpected token \"}\" in runtime") },
 
     { njs_str("new Function('`'.repeat(2**13));"),
       njs_str("[object Function]") },
 
     { njs_str("new Function('{['.repeat(2**13));"),
-      njs_str("SyntaxError: Unexpected token \"}\" in runtime:1") },
+      njs_str("SyntaxError: Unexpected token \"}\" in runtime") },
 
     { njs_str("new Function('{;'.repeat(2**13));"),
-      njs_str("SyntaxError: Unexpected token \")\" in runtime:1") },
+      njs_str("SyntaxError: Unexpected token \")\" in runtime") },
 
     { njs_str("(new Function('1;'.repeat(2**13) + 'return 2'))()"),
       njs_str("2") },
@@ -14216,7 +14216,7 @@
       njs_str("-4") },
 
     { njs_str("new Function('new '.repeat(2**13));"),
-      njs_str("SyntaxError: Unexpected token \"}\" in runtime:1") },
+      njs_str("SyntaxError: Unexpected token \"}\" in runtime") },
 
     { njs_str("(new Function('return ' + 'typeof '.repeat(2**13) + 'x'))()"),
       njs_str("string") },
@@ -14282,7 +14282,13 @@
       njs_str("ReferenceError: \"foo\" is not defined") },
 
     { njs_str("this.NN = {}; var f = Function('eval = 42;'); f()"),
-      njs_str("SyntaxError: Identifier \"eval\" is forbidden as left-hand in assignment in runtime:1") },
+      njs_str("SyntaxError: Identifier \"eval\" is forbidden as left-hand in assignment in runtime") },
+
+    { njs_str("new Function('}); let a; a; function o(){}; //')"),
+      njs_str("SyntaxError: Unexpected token \"}\" in runtime") },
+
+    { njs_str("new Function('}); let a; a; function o(){}; ({')"),
+      njs_str("SyntaxError: single function literal required") },
 
     { njs_str("RegExp()"),
       njs_str("/(?:)/") },
@@ -19811,7 +19817,7 @@
       njs_str("[object AsyncFunction]") },
 
     { njs_str("let f = new Function('x', 'await 1; return x'); f(1)"),
-      njs_str("SyntaxError: await is only valid in async functions in runtime:1") },
+      njs_str("SyntaxError: await is only valid in async functions in runtime") },
 
     { njs_str("new AsyncFunction()"),
       njs_str("ReferenceError: \"AsyncFunction\" is not defined") },
@@ -21676,7 +21682,9 @@
         return NJS_ERROR;
     }
 
-    success = njs_strstr_eq(&expected->ret, &s);
+    success = expected->ret.length <= s.length
+              && (memcmp(expected->ret.start, s.start, expected->ret.length)
+                  == 0);
     if (!success) {
         njs_stderror("njs(\"%V\")\nexpected: \"%V\"\n     got: \"%V\"\n",
                      &expected->script, &expected->ret, &s);