From: Dmitry Volyntsev Date: Thu, 12 Nov 2026 01:34:31 +0000 (-0800) Subject: QuickJS: fix njs.on() replacing the exit hook during its call X-Git-Url: https://git.kaiwu.me/ngx_http_auth_basic_module.html?a=commitdiff_plain;h=db7ecabe387822f80b978971c5bb90371ecd77b4;p=njs.git QuickJS: fix njs.on() replacing the exit hook during its call Previously, the exit hook was released after the call while the caller kept no reference to it. If the hook replaced or reset itself with njs.on(), the function was freed while it was still running, which resulted in use-after-free. Now the hook is detached from the njs object before the call, and the call owns the reference. The issue was introduced in 04f6dfb9 (0.9.2). --- diff --git a/src/qjs.c b/src/qjs.c index 5220486b..2ab6ed0c 100644 --- a/src/qjs.c +++ b/src/qjs.c @@ -355,7 +355,7 @@ qjs_chb_rt_free(void *rt, void *p) JSValue qjs_call_exit_hook(JSContext *ctx) { - JSValue global, obj, ret; + JSValue global, obj, hook, ret; qjs_njs_t *njs; global = JS_GetGlobalObject(ctx); @@ -367,12 +367,13 @@ qjs_call_exit_hook(JSContext *ctx) njs = JS_GetOpaque(obj, QJS_CORE_CLASS_ID_NJS); if (njs != NULL && JS_IsFunction(ctx, njs->hooks[QJS_NJS_HOOK_EXIT])) { - ret = JS_Call(ctx, njs->hooks[QJS_NJS_HOOK_EXIT], JS_UNDEFINED, - 0, NULL); - - JS_FreeValue(ctx, njs->hooks[QJS_NJS_HOOK_EXIT]); + hook = njs->hooks[QJS_NJS_HOOK_EXIT]; njs->hooks[QJS_NJS_HOOK_EXIT] = JS_UNDEFINED; + ret = JS_Call(ctx, hook, JS_UNDEFINED, 0, NULL); + + JS_FreeValue(ctx, hook); + if (JS_IsException(ret)) { JS_FreeValue(ctx, obj); JS_FreeValue(ctx, global); diff --git a/test/js/njs_on_exit_replace.t.js b/test/js/njs_on_exit_replace.t.js new file mode 100644 index 00000000..f005523a --- /dev/null +++ b/test/js/njs_on_exit_replace.t.js @@ -0,0 +1,24 @@ +/*--- +includes: [] +flags: [] +---*/ + +if (typeof njs == 'object' && typeof njs.on == 'function') { + (function() { + let o = {v: 'x'}; + + njs.on('exit', function() { + njs.on('exit', null); + + let a = []; + + for (let i = 0; i < 100; i++) { + a.push({i}); + } + + if (o.v !== 'x') { + throw Error('closure is lost'); + } + }); + })(); +}