CVE-2022-25139 (published 2022-02-14T22:15Z)

njs through 0.7.0, used in NGINX, was discovered to contain a heap use-after-free in njs_await_fulfilled.

Severity: HIGH
CVSS v2 base score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 9.8, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-416
cwe_name: Use After Free
description: The product reuses or references memory after it has been freed. At some point afterward, the memory may be allocated again and saved in another pointer, while the original pointer references a location somewhere within the new allocation. Any operations using the original pointer are no longer valid because the memory "belongs" to the code that operates on the new pointer.
extended_description: 
             
             
             
			 
             
             
         
         

Fix information:
repo_url: https://github.com/nginx/njs
commit_details: repo_url: https://github.com/nginx/njs
author: Dmitry Volyntsev
author_date: 2022-01-21 14:31:30+00:00
msg: Fixed recursive async function calls.

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.
repository: repo_url: https://github.com/nginx/njs
repo_name: nginx/njs
description: A subset of JavaScript language to use in nginx
repo_language: C
owner: nginx
file_changes: filename: njs_async.c
change_type: ModificationType.MODIFY
diff: @@ -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;

programming_language: C
filename: njs_function.c
change_type: ModificationType.MODIFY
diff: @@ -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);
     }
 }
 

programming_language: C
filename: njs_function.h
change_type: ModificationType.MODIFY
diff: @@ -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);

programming_language: C++
filename: njs_value.h
change_type: ModificationType.MODIFY
diff: @@ -270,7 +270,6 @@ struct njs_function_s {
     } u;
 
     void                              *context;
-    void                              *await;
 
     njs_value_t                       *bound;
 };

programming_language: SQL
filename: njs_vm.c
change_type: ModificationType.MODIFY
diff: @@ -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;
 }

programming_language: C++
filename: njs_vmcode.c
change_type: ModificationType.MODIFY
diff: @@ -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)) {

programming_language: SQL
filename: njs_vmcode.h
change_type: ModificationType.MODIFY
diff: @@ -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);
 

programming_language: C++
filename: async_recursive_last.t.js
change_type: ModificationType.ADD
diff: @@ -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);

programming_language: TypeScript
filename: async_recursive_mid.t.js
change_type: ModificationType.ADD
diff: @@ -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);

programming_language: TypeScript