CVE-2022-27007 (published 2022-04-14T15:15Z)

nginx njs 0.7.2 is affected suffers from Use-after-free in njs_function_frame_alloc() when it try to invoke from a restored frame saved with njs_function_frame_save().

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-02-21 16:52:59+00:00
msg: Fixed frame allocation from an awaited frame.

njs_function_frame_save() is used to save the awaited frame when "await"
instruction is encountered. The saving was done as a memcpy() of
existing runtime frame.

njs_function_frame_alloc() is used to alloc a new function frame, this
function tries to use a spare preallocated memory from the previous
frame first.  Previously, this function might result in "use-after-free"
when invoked from a restored frame saved with njs_function_frame_save().
Because njs_function_frame_save() left pointers to the spare memory of
the original frame which may be already free when saved frame is
restored.

The fix is to erase fields for the spare memory from the saved frame.

This closes #469 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_function.c
change_type: ModificationType.MODIFY
diff: @@ -811,9 +811,13 @@ njs_function_frame_save(njs_vm_t *vm, njs_frame_t *frame, u_char *pc)
     njs_native_frame_t  *active, *native;
 
     *frame = *vm->active_frame;
+
     frame->previous_active_frame = NULL;
 
     native = &frame->native;
+    native->size = 0;
+    native->free = NULL;
+    native->free_size = 0;
 
     active = &vm->active_frame->native;
     value_count = njs_function_frame_value_count(active);

programming_language: C
filename: async_recursive_large.t.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,26 @@
+/*---
+includes: [compareArray.js]
+flags: [async]
+---*/
+
+let stages = [];
+
+async function f(v) {
+    if (v == 1000) {
+        return;
+    }
+
+    stages.push(`f>${v}`);
+
+    await "X";
+
+    await f(v + 1);
+
+    stages.push(`f<${v}`);
+}
+
+f(0)
+.then(v => {
+    assert.sameValue(stages.length, 2000);
+})
+.then($DONE, $DONE);

programming_language: TypeScript
filename: async_recursive_mid.t.js
change_type: ModificationType.MODIFY
diff: @@ -6,7 +6,7 @@ flags: [async]
 let stages = [];
 
 async function f(v) {
-    if (v == 3) {
+    if (v == 1000) {
         return;
     }
 

programming_language: TypeScript