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