From: Dmitry Volyntsev Date: Wed, 11 Nov 2026 01:46:20 +0000 (-0800) Subject: Modules: initialize the JS context of a completed subrequest X-Git-Url: https://git.kaiwu.me/ngx_http_auth_basic_module.html?a=commitdiff_plain;h=76bf6406543d451bb86f1aeede23f74d671ce9be;p=njs.git Modules: initialize the JS context of a completed subrequest When a subrequest completed, its JS context was allocated by the subrequest completion handler without ngx_js_ctx_init(), so the log and the waiting events tree were not initialized. If the subrequest had no JS context of its own, a reply received by the parent could then use it: - readRequest*() on the reply inserted a caller event into the uninitialized tree; - reading a js_set variable through reply.variables created an engine for the uninitialized context. Both crashed with both engines. The waiting events tree is missing since 9de30135 (0.8.3), the log since 9b674412 (0.8.6). Now the context is initialized as in ngx_http_js_init_vm(). Also, deferred body reads are allowed only for the executing request, because the body read state of a reply belongs to the subrequest. Changed behaviour: - readRequestText() and the other readRequest*() methods called on a subrequest reply throw TypeError. They used to resolve with the body of the parent request, or to crash when the body had to be read. --- diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index f85b0b65..ad9649ff 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -3950,6 +3950,11 @@ ngx_http_js_ext_read_request_body(njs_vm_t *vm, njs_value_t *args, return NJS_ERROR; } + if (r != njs_vm_external_ptr(vm)) { + njs_vm_type_error(vm, "cannot read body of another request"); + return NJS_ERROR; + } + ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); event = njs_mp_zalloc(njs_vm_memory_pool(vm), @@ -4061,6 +4066,11 @@ ngx_http_js_ext_read_request_form(njs_vm_t *vm, njs_value_t *args, return NJS_ERROR; } + if (r != njs_vm_external_ptr(vm)) { + njs_vm_type_error(vm, "cannot read body of another request"); + return NJS_ERROR; + } + if (ngx_http_js_request_form_max_keys(vm, njs_arg(args, nargs, 1), &max_keys) != NJS_OK) @@ -5066,6 +5076,8 @@ ngx_http_js_subrequest_done(ngx_http_request_t *r, void *data, ngx_int_t rc) return NGX_ERROR; } + ngx_js_ctx_init((ngx_js_ctx_t *) ctx, r->connection->log); + ngx_http_set_ctx(r, ctx, ngx_http_js_module); } @@ -6788,6 +6800,11 @@ ngx_http_qjs_ext_read_request_body(JSContext *cx, JSValueConst this_val, } r = req->opaque.external; + + if (r != JS_GetContextOpaque(cx)) { + return JS_ThrowTypeError(cx, "cannot read body of another request"); + } + ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); if (r->request_body) { @@ -6943,6 +6960,11 @@ ngx_http_qjs_ext_read_request_form(JSContext *cx, JSValueConst this_val, } r = req->opaque.external; + + if (r != JS_GetContextOpaque(cx)) { + return JS_ThrowTypeError(cx, "cannot read body of another request"); + } + ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); if (r->request_body) { @@ -7668,6 +7690,8 @@ ngx_http_qjs_subrequest_done(ngx_http_request_t *r, void *data, ngx_int_t rc) return NGX_ERROR; } + ngx_js_ctx_init((ngx_js_ctx_t *) sctx, r->connection->log); + ngx_http_set_ctx(r, sctx, ngx_http_js_module); ngx_qjs_arg(sctx->response_body) = JS_UNDEFINED; diff --git a/nginx/t/js_http_runtime_isolation.t b/nginx/t/js_http_runtime_isolation.t index a8b979aa..e85fbd50 100644 --- a/nginx/t/js_http_runtime_isolation.t +++ b/nginx/t/js_http_runtime_isolation.t @@ -37,6 +37,8 @@ http { js_import test.js; js_set $retval test.variable; + js_set $reply_uri test.reply_uri; + js_var $read_result; server { listen 127.0.0.1:8080; @@ -69,6 +71,19 @@ http { location /variable { return 200 $retval; } + + location /access_read { + js_access test.access_read; + js_content test.read_result; + } + + location /content_read { + js_content test.content_read; + } + + location /reply_variable { + js_content test.reply_variable; + } } } @@ -139,10 +154,45 @@ $t->write_file('test.js', <<'EOF'); r.setReturnValue('own result'); } - export default {content, child, variable}; + function read_reply(reply) { + return ['readRequestText', 'readRequestForm'].map(method => { + try { + reply[method](); + return 'accepted'; + } catch (e) { + return e instanceof TypeError ? 'rejected' : e.name; + } + }).join(','); + } + + async function access_read(r) { + const reply = await r.subrequest('/native'); + r.variables.read_result = read_reply(reply); + } + + function read_result(r) { + r.return(200, r.variables.read_result); + } + + async function content_read(r) { + const reply = await r.subrequest('/native'); + r.return(200, read_reply(reply)); + } + + function reply_uri(r) { + return r.uri; + } + + async function reply_variable(r) { + const reply = await r.subrequest('/native'); + r.return(200, reply.variables.reply_uri); + } + + export default {content, child, variable, access_read, read_result, + content_read, reply_uri, reply_variable}; EOF -$t->try_run('no njs available')->plan(19); +$t->try_run('no njs available')->plan(22); for my $scope ('zero', 'reuse') { for my $mode ('parent', 'shape', 'args', 'parent_set') { @@ -162,5 +212,10 @@ for my $scope ('zero', 'reuse') { } like(http_get('/variable'), qr/own result$/m, 'own async js_set result'); +like(http_get('/access_read'), qr/rejected,rejected$/m, + 'reply body read in access handler'); +like(http_get('/content_read'), qr/rejected,rejected$/m, + 'reply body read in content handler'); +like(http_get('/reply_variable'), qr/\/native$/m, 'reply js_set variable'); ###############################################################################