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

njs through 0.7.1, used in NGINX, was discovered to contain a control flow hijack caused by a Type Confusion vulnerability in njs_promise_perform_then().

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-843
cwe_name: Access of Resource Using Incompatible Type ('Type Confusion')
description: The product allocates or initializes a resource such as a pointer, object, or variable using one type, but it later accesses that resource using a type that is incompatible with the original type.
extended_description: 
            When the product accesses the resource using an incompatible type, this could trigger logical errors because the resource does not have expected properties. In languages without memory safety, such as C and C++, type confusion can lead to out-of-bounds memory access.
            While this weakness is frequently associated with unions when parsing data with many different embedded object types in C, it can be present in any application that can interpret the same variable or memory location in multiple ways.
            This weakness is not unique to C and C++. For example, errors in PHP applications can be triggered by providing array parameters when scalars are expected, or vice versa. Languages such as Perl, which perform automatic conversion of a variable of one type when it is accessed as if it were another type, can also contain these issues.
         
         

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-19 13:12:09+00:00
msg: Fixed type confusion bug while resolving promises.

Previously, the internal function njs_promise_perform_then() which
implements PerformPromiseThen() expects its first argument to always be
a promise instance.  This assertion might be invalid because the
functions corresponding to Promise.prototype.then() and
Promise.resolve() incorrectly verified their arguments.

Specifically, the functions recognized their first argument as promise
if it was an object which was an Promise or had Promise object in its
prototype chain.  The later condition is not correct because internal
slots are not inherited according to the spec.

This closes #447 issue in 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_promise.c
change_type: ModificationType.MODIFY
diff: @@ -771,25 +771,19 @@ njs_promise_resolve(njs_vm_t *vm, njs_value_t *constructor, njs_value_t *x)
 {
     njs_int_t                 ret;
     njs_value_t               value;
-    njs_object_t              *object;
     njs_promise_capability_t  *capability;
 
     static const njs_value_t  string_constructor = njs_string("constructor");
 
-    if (njs_is_object(x)) {
-        object = njs_object_proto_lookup(njs_object(x), NJS_PROMISE,
-                                         njs_object_t);
-
-        if (object != NULL) {
-            ret = njs_value_property(vm, x, njs_value_arg(&string_constructor),
-                                     &value);
-            if (njs_slow_path(ret == NJS_ERROR)) {
-                return NULL;
-            }
+    if (njs_is_promise(x)) {
+        ret = njs_value_property(vm, x, njs_value_arg(&string_constructor),
+                                 &value);
+        if (njs_slow_path(ret == NJS_ERROR)) {
+            return NULL;
+        }
 
-            if (njs_values_same(&value, constructor)) {
-                return njs_promise(x);
-            }
+        if (njs_values_same(&value, constructor)) {
+            return njs_promise(x);
         }
     }
 
@@ -875,19 +869,12 @@ njs_promise_prototype_then(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
 {
     njs_int_t                 ret;
     njs_value_t               *promise, *fulfilled, *rejected, constructor;
-    njs_object_t              *object;
     njs_function_t            *function;
     njs_promise_capability_t  *capability;
 
     promise = njs_argument(args, 0);
 
-    if (njs_slow_path(!njs_is_object(promise))) {
-        goto failed;
-    }
-
-    object = njs_object_proto_lookup(njs_object(promise), NJS_PROMISE,
-                                     njs_object_t);
-    if (njs_slow_path(object == NULL)) {
+    if (njs_slow_path(!njs_is_promise(promise))) {
         goto failed;
     }
 
@@ -933,6 +920,8 @@ njs_promise_perform_then(njs_vm_t *vm, njs_value_t *value,
     njs_promise_data_t      *data;
     njs_promise_reaction_t  *fulfilled_reaction, *rejected_reaction;
 
+    njs_assert(njs_is_promise(value));
+
     if (!njs_is_function(fulfilled)) {
         fulfilled = njs_value_arg(&njs_value_undefined);
     }

programming_language: C
filename: njs_vmcode.c
change_type: ModificationType.MODIFY
diff: @@ -1895,7 +1895,7 @@ njs_vmcode_await(njs_vm_t *vm, njs_vmcode_await_t *await)
     rejected->args_count = 1;
     rejected->u.native = njs_await_rejected;
 
-    njs_set_object(&val, &promise->object);
+    njs_set_promise(&val, promise);
     njs_set_function(&on_fulfilled, fulfilled);
     njs_set_function(&on_rejected, rejected);
 

programming_language: SQL
filename: promise_prototype_reject_type_confusion.t.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,11 @@
+/*---
+includes: []
+flags: [async]
+---*/
+
+Symbol.__proto__ = new Promise(()=>{});
+
+Promise.reject(Symbol)
+.then(v => $DONOTEVALUATE())
+.catch(err => assert.sameValue(err.name, 'Symbol'))
+.then($DONE, $DONE);

programming_language: Markdown
filename: promise_prototype_then_type_confusion.t.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,11 @@
+/*---
+includes: []
+flags: [async]
+---*/
+
+Symbol.__proto__ = new Promise(()=>{});
+
+Promise.resolve(Symbol)
+.then(v => $DONOTEVALUATE())
+.catch(err => assert.sameValue(err.name, 'TypeError'))
+.then($DONE, $DONE);

programming_language: Markdown