CVE-2020-1915 (published 2020-10-26T21:15Z)

An out-of-bounds read in the JavaScript Interpreter in Facebook Hermes prior to commit 8cb935cd3b2321c46aa6b7ed8454d95c75a7fca0 allows attackers to cause a denial of service attack or possible further memory corruption via crafted JavaScript. Note that this is only exploitable if the application using Hermes permits evaluation of untrusted JavaScript. Hence, most React Native applications are not affected.

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

Weakness information:
cwe_id: CWE-125
cwe_name: Out-of-bounds Read
description: The product reads data past the end, or before the beginning, of the intended buffer.
extended_description: 
            
            
             
			 
         
         

Fix information:
repo_url: https://github.com/facebook/hermes
commit_details: repo_url: https://github.com/facebook/hermes
author: Marc Horowitz
author_date: 2020-09-25 20:52:54-07:00
msg: Handle set where internalSetter and Proxy are both true

Summary:
If putComputed is called on a proxy whose target's prototype
is an array with a propname of 'length', then internalSetter will be
true, and the receiver will be a proxy.  In that case, proxy needs to
win; the behavior may assert or be UB otherwise.

Reviewed By: tmikov

Differential Revision: D23916279

fbshipit-source-id: c760356d48a02ece565fb4bc1acdafd7ccad7c68
repository: repo_url: https://github.com/facebook/hermes
repo_name: facebook/hermes
description: A JavaScript engine optimized for running React Native.
repo_language: C++
owner: facebook
file_changes: filename: JSObject.cpp
change_type: ModificationType.MODIFY
diff: @@ -1699,6 +1699,24 @@ CallResult<bool> JSObject::putComputedWithReceiver_RJS(
     if (LLVM_UNLIKELY(
             desc.flags.internalSetter || receiverHandle->isHostObject() ||
             receiverHandle->isProxyObject())) {
+      // If putComputed is called on a proxy whose target's prototype
+      // is an array with a propname of 'length', then internalSetter
+      // will be true, and the receiver will be a proxy.  In that case,
+      // proxy wins.
+      if (receiverHandle->isProxyObject()) {
+        if (*descDefinedRes) {
+          dpf.setValue = 1;
+        } else {
+          dpf = DefinePropertyFlags::getDefaultNewPropertyFlags();
+        }
+        return JSProxy::defineOwnProperty(
+            receiverHandle,
+            runtime,
+            nameValPrimitiveHandle,
+            dpf,
+            valueHandle,
+            opFlags);
+      }
       SymbolID id{};
       LAZY_TO_IDENTIFIER(runtime, nameValPrimitiveHandle, id);
       if (desc.flags.internalSetter) {
@@ -1709,23 +1727,10 @@ CallResult<bool> JSObject::putComputedWithReceiver_RJS(
             desc.castToNamedPropertyDescriptorRef(),
             valueHandle,
             opFlags);
-      } else if (receiverHandle->isHostObject()) {
-        return vmcast<HostObject>(receiverHandle.get())->set(id, *valueHandle);
       }
       assert(
-          receiverHandle->isProxyObject() && "descriptor flags are impossible");
-      if (*descDefinedRes) {
-        dpf.setValue = 1;
-      } else {
-        dpf = DefinePropertyFlags::getDefaultNewPropertyFlags();
-      }
-      return JSProxy::defineOwnProperty(
-          receiverHandle,
-          runtime,
-          nameValPrimitiveHandle,
-          dpf,
-          valueHandle,
-          opFlags);
+          receiverHandle->isHostObject() && "descriptor flags are impossible");
+      return vmcast<HostObject>(receiverHandle.get())->set(id, *valueHandle);
     }
   }
 

programming_language: C++
filename: proxy.js
change_type: ModificationType.MODIFY
diff: @@ -2125,5 +2125,14 @@ checkDeep({...f})(_ => ({a:1, b:2}))
 // uses fast array access (this will trip an assert otherwise)
 new Proxy([], {}).unshift(0);
 
+// If putComputed is called on a proxy whose target's prototype is an
+// array with a propname of 'length', then internalSetter will be
+// true, and the receiver will be a proxy.  In that case, proxy needs
+// to win; the behavior may assert or be UB otherwise.
+var p = new Proxy(Object.create([]), {});
+// using String() forces putComputed
+p[String('length')] = 0x123;
+p[0xABC] = 1111;
+
 print('done');
 // CHECK-LABEL: done

programming_language: JavaScript