[njs] Fixed Function constructor template injection.
noreply at nginx.com
noreply at nginx.com
Wed Jun 4 05:22:02 UTC 2025
details: https://github.com/nginx/njs/commit/aa358e16337e535240d7976c9232dd06fe0ef5d8
branches: master
commit: aa358e16337e535240d7976c9232dd06fe0ef5d8
user: Dmitry Volyntsev <xeioex at 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);
More information about the nginx-devel
mailing list