]> git.kaiwu.me - njs.git/commitdiff
Fixed recursive async function calls.
authorDmitry Volyntsev <xeioex@nginx.com>
Fri, 21 Jan 2022 14:31:30 +0000 (14:31 +0000)
committerDmitry Volyntsev <xeioex@nginx.com>
Fri, 21 Jan 2022 14:31:30 +0000 (14:31 +0000)
Previously, PromiseCapability record was stored (function->context)
directly in function object during a function invocation.  This is
not correct, because PromiseCapability record should be linked to
current execution context.  As a result, function->context is
overwritten with consecutive recursive calls which results in
use-after-free.

This closes #451 issue on Github.

src/njs_async.c
src/njs_function.c
src/njs_function.h
src/njs_value.h
src/njs_vm.c
src/njs_vmcode.c
src/njs_vmcode.h
test/js/async_recursive_last.t.js [new file with mode: 0644]
test/js/async_recursive_mid.t.js [new file with mode: 0644]

index 7bc6c37e79fd19aad2a4812079ad5b2ad7708593..e4dd74863e3fbc3695b2a82a5265c5816eed528d 100644 (file)
@@ -29,9 +29,7 @@ njs_async_function_frame_invoke(njs_vm_t *vm, njs_value_t *retval)
         return NJS_ERROR;
     }
 
-    frame->function->context = capability;
-
-    ret = njs_function_lambda_call(vm);
+    ret = njs_function_lambda_call(vm, capability, NULL);
 
     if (ret == NJS_OK) {
         ret = njs_function_call(vm, njs_function(&capability->resolve),
@@ -63,7 +61,6 @@ njs_await_fulfilled(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     njs_int_t           ret;
     njs_value_t         **cur_local, **cur_closures, **cur_temp, *value;
     njs_frame_t         *frame, *async_frame;
-    njs_function_t      *function;
     njs_async_ctx_t     *ctx;
     njs_native_frame_t  *top, *async;
 
@@ -78,8 +75,6 @@ njs_await_fulfilled(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     async = &async_frame->native;
     async->previous = vm->top_frame;
 
-    function = async->function;
-
     cur_local = vm->levels[NJS_LEVEL_LOCAL];
     cur_closures = vm->levels[NJS_LEVEL_CLOSURE];
     cur_temp = vm->levels[NJS_LEVEL_TEMP];
@@ -98,13 +93,7 @@ njs_await_fulfilled(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
 
     vm->top_frame->retval = &vm->retval;
 
-    function->context = ctx->capability;
-    function->await = ctx;
-
-    ret = njs_vmcode_interpreter(vm, ctx->pc);
-
-    function->context = NULL;
-    function->await = NULL;
+    ret = njs_vmcode_interpreter(vm, ctx->pc, ctx->capability, ctx);
 
     vm->levels[NJS_LEVEL_LOCAL] = cur_local;
     vm->levels[NJS_LEVEL_CLOSURE] = cur_closures;
index ae0fa11ff8e2e6da6abab02ef7425c2c7686b4f2..5f07ddd9caaab808532455a965d836b53aa99c48 100644 (file)
@@ -608,7 +608,7 @@ njs_function_call2(njs_vm_t *vm, njs_function_t *function,
 
 
 njs_int_t
-njs_function_lambda_call(njs_vm_t *vm)
+njs_function_lambda_call(njs_vm_t *vm, void *promise_cap, void *async_ctx)
 {
     uint32_t               n;
     njs_int_t              ret;
@@ -622,6 +622,8 @@ njs_function_lambda_call(njs_vm_t *vm)
     frame = (njs_frame_t *) vm->top_frame;
     function = frame->native.function;
 
+    njs_assert(function->context == NULL);
+
     if (function->global && !function->closure_copied) {
         ret = njs_function_capture_global_closures(vm, function);
         if (njs_slow_path(ret != NJS_OK)) {
@@ -698,7 +700,7 @@ njs_function_lambda_call(njs_vm_t *vm)
         }
     }
 
-    ret = njs_vmcode_interpreter(vm, lambda->start);
+    ret = njs_vmcode_interpreter(vm, lambda->start, promise_cap, async_ctx);
 
     /* Restore current level. */
     vm->levels[NJS_LEVEL_LOCAL] = cur_local;
@@ -775,7 +777,7 @@ njs_function_frame_invoke(njs_vm_t *vm, njs_value_t *retval)
         return njs_function_native_call(vm);
 
     } else {
-        return njs_function_lambda_call(vm);
+        return njs_function_lambda_call(vm, NULL, NULL);
     }
 }
 
index b47e7dc6af1f90cdc79b1c2b8b01c98959f3bd9b..59150fdd5c94ec08871615e9f034daaf010e9273 100644 (file)
@@ -112,7 +112,8 @@ njs_int_t njs_function_lambda_frame(njs_vm_t *vm, njs_function_t *function,
 njs_int_t njs_function_call2(njs_vm_t *vm, njs_function_t *function,
     const njs_value_t *this, const njs_value_t *args,
     njs_uint_t nargs, njs_value_t *retval, njs_bool_t ctor);
-njs_int_t njs_function_lambda_call(njs_vm_t *vm);
+njs_int_t njs_function_lambda_call(njs_vm_t *vm, void *promise_cap,
+    void *async_ctx);
 njs_int_t njs_function_native_call(njs_vm_t *vm);
 njs_native_frame_t *njs_function_frame_alloc(njs_vm_t *vm, size_t size);
 void njs_function_frame_free(njs_vm_t *vm, njs_native_frame_t *frame);
index 12448eacd3a6bd5b8106cd65067d6fa0035a685e..7297df3953d7b5640e201e7d29297235f9222dba 100644 (file)
@@ -270,7 +270,6 @@ struct njs_function_s {
     } u;
 
     void                              *context;
-    void                              *await;
 
     njs_value_t                       *bound;
 };
index b46f88c628bc07a21a875afcb6698dfb4b273bd4..0c75fff71d2cccfff9d3d415522beed5e34e0f38 100644 (file)
@@ -490,7 +490,7 @@ njs_vm_start(njs_vm_t *vm)
         return ret;
     }
 
-    ret = njs_vmcode_interpreter(vm, vm->start);
+    ret = njs_vmcode_interpreter(vm, vm->start, NULL, NULL);
 
     return (ret == NJS_ERROR) ? NJS_ERROR : NJS_OK;
 }
index b371c3748e88a2a9e13ba1487d340eb4743b39ea..3039642cb9a3ee75fc878bd87980eecc5937c3c2 100644 (file)
@@ -42,7 +42,8 @@ static njs_jump_off_t njs_vmcode_debugger(njs_vm_t *vm);
 static njs_jump_off_t njs_vmcode_return(njs_vm_t *vm, njs_value_t *invld,
     njs_value_t *retval);
 
-static njs_jump_off_t njs_vmcode_await(njs_vm_t *vm, njs_vmcode_await_t *await);
+static njs_jump_off_t njs_vmcode_await(njs_vm_t *vm, njs_vmcode_await_t *await,
+    njs_promise_capability_t *pcap, njs_async_ctx_t *actx);
 
 static njs_jump_off_t njs_vmcode_try_start(njs_vm_t *vm, njs_value_t *value,
     njs_value_t *offset, u_char *pc);
@@ -77,7 +78,8 @@ static njs_jump_off_t njs_function_frame_create(njs_vm_t *vm,
 
 
 njs_int_t
-njs_vmcode_interpreter(njs_vm_t *vm, u_char *pc)
+njs_vmcode_interpreter(njs_vm_t *vm, u_char *pc, void *promise_cap,
+    void *async_ctx)
 {
     u_char                       *catch;
     double                       num, exponent;
@@ -826,7 +828,7 @@ next:
 
             case NJS_VMCODE_AWAIT:
                 await = (njs_vmcode_await_t *) pc;
-                return njs_vmcode_await(vm, await);
+                return njs_vmcode_await(vm, await, promise_cap, async_ctx);
 
             case NJS_VMCODE_TRY_START:
                 ret = njs_vmcode_try_start(vm, value1, value2, pc);
@@ -1812,7 +1814,8 @@ njs_vmcode_return(njs_vm_t *vm, njs_value_t *invld, njs_value_t *retval)
 
 
 static njs_jump_off_t
-njs_vmcode_await(njs_vm_t *vm, njs_vmcode_await_t *await)
+njs_vmcode_await(njs_vm_t *vm, njs_vmcode_await_t *await,
+    njs_promise_capability_t *pcap, njs_async_ctx_t *ctx)
 {
     size_t              size;
     njs_int_t           ret;
@@ -1820,7 +1823,6 @@ njs_vmcode_await(njs_vm_t *vm, njs_vmcode_await_t *await)
     njs_value_t         ctor, val, on_fulfilled, on_rejected, *value;
     njs_promise_t       *promise;
     njs_function_t      *fulfilled, *rejected;
-    njs_async_ctx_t     *ctx;
     njs_native_frame_t  *active;
 
     active = &vm->active_frame->native;
@@ -1837,8 +1839,6 @@ njs_vmcode_await(njs_vm_t *vm, njs_vmcode_await_t *await)
         return NJS_ERROR;
     }
 
-    ctx = active->function->await;
-
     if (ctx == NULL) {
         ctx = njs_mp_alloc(vm->mem_pool, sizeof(njs_async_ctx_t));
         if (njs_slow_path(ctx == NULL)) {
@@ -1854,9 +1854,7 @@ njs_vmcode_await(njs_vm_t *vm, njs_vmcode_await_t *await)
         }
 
         ctx->await = fulfilled->context;
-        ctx->capability = active->function->context;
-
-        active->function->context = NULL;
+        ctx->capability = pcap;
 
         ret = njs_function_frame_save(vm, ctx->await, NULL);
         if (njs_slow_path(ret != NJS_OK)) {
index c15a4bd9e740ee1c5ee001b76d724770d3eca6f3..6b8f65582baab4d1eb9d52e730523c83eccdc2c1 100644 (file)
@@ -437,7 +437,8 @@ typedef struct {
 } njs_vmcode_await_t;
 
 
-njs_int_t njs_vmcode_interpreter(njs_vm_t *vm, u_char *pc);
+njs_int_t njs_vmcode_interpreter(njs_vm_t *vm, u_char *pc,
+    void *promise_cap, void *async_ctx);
 
 njs_object_t *njs_function_new_object(njs_vm_t *vm, njs_value_t *constructor);
 
diff --git a/test/js/async_recursive_last.t.js b/test/js/async_recursive_last.t.js
new file mode 100644 (file)
index 0000000..84f1b57
--- /dev/null
@@ -0,0 +1,26 @@
+/*---
+includes: [compareArray.js]
+flags: [async]
+---*/
+
+let stages = [];
+
+async function f(v) {
+    if (v == 3) {
+        return;
+    }
+
+    stages.push(`f>${v}`);
+
+    f(v + 1);
+
+    stages.push(`f<${v}`);
+
+    await "X";
+}
+
+f(0)
+.then(v => {
+    assert.compareArray(stages, ['f>0', 'f>1', 'f>2', 'f<2', 'f<1', 'f<0']);
+})
+.then($DONE, $DONE);
diff --git a/test/js/async_recursive_mid.t.js b/test/js/async_recursive_mid.t.js
new file mode 100644 (file)
index 0000000..4d3a9fd
--- /dev/null
@@ -0,0 +1,26 @@
+/*---
+includes: [compareArray.js]
+flags: [async]
+---*/
+
+let stages = [];
+
+async function f(v) {
+    if (v == 3) {
+        return;
+    }
+
+    stages.push(`f>${v}`);
+
+    await "X";
+
+    f(v + 1);
+
+    stages.push(`f<${v}`);
+}
+
+f(0)
+.then(v => {
+    assert.compareArray(stages, ['f>0','f>1','f<0','f>2','f<1']);
+})
+.then($DONE, $DONE);