]> git.kaiwu.me - njs.git/commitdiff
Fixed Function constructor template injection.
authorDmitry Volyntsev <xeioex@nginx.com>
Tue, 3 Jun 2025 00:18:22 +0000 (17:18 -0700)
committerDmitry Volyntsev <xeioexception@gmail.com>
Wed, 4 Jun 2025 05:21:03 +0000 (22:21 -0700)
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
src/test/njs_unit_test.c

index 0840d437eef992130a936dff958210f78188e5bf..1db0d7abfb4f67254e190a4cb7f59db84ed977a3 100644 (file)
@@ -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)) {
index 27fcbd82a36ee5195cca1f8a516b96049a8c64ea..3d466b9601ab66db07a48243f3729278e68243b8 100644 (file)
@@ -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);