details:   
https://github.com/nginx/njs/commit/aa358e16337e535240d7976c9232dd06fe0ef5d8
branches:  master
commit:    aa358e16337e535240d7976c9232dd06fe0ef5d8
user:      Dmitry Volyntsev <xei...@nginx.com>
date:      Mon, 2 Jun 2025 17:18:22 -0700
description:
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.

---
 src/njs_function.c       | 14 +++++++++++---
 src/test/njs_unit_test.c | 26 +++++++++++++++++---------
 2 files changed, 28 insertions(+), 12 deletions(-)

diff --git a/src/njs_function.c b/src/njs_function.c
index 0840d437..1db0d7ab 100644
--- a/src/njs_function.c
+++ b/src/njs_function.c
@@ -1049,7 +1049,7 @@ njs_function_constructor(njs_vm_t *vm, njs_value_t *args, 
njs_uint_t nargs,
         }
     }
 
-    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_function_constructor(njs_vm_t *vm, njs_value_t *args, 
njs_uint_t nargs,
         }
     }
 
-    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_function_constructor(njs_vm_t *vm, njs_value_t 
*args, njs_uint_t nargs,
 
     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)) {
diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c
index 27fcbd82..3d466b96 100644
--- a/src/test/njs_unit_test.c
+++ b/src/test/njs_unit_test.c
@@ -14189,22 +14189,22 @@ static njs_unit_test_t  njs_test[] =
       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 @@ static njs_unit_test_t  njs_test[] =
       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 @@ static njs_unit_test_t  njs_test[] =
       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 @@ static njs_unit_test_t  njs_test[] =
       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 @@ done:
         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);
_______________________________________________
nginx-devel mailing list
nginx-devel@nginx.org
https://mailman.nginx.org/mailman/listinfo/nginx-devel

Reply via email to