CVE-2023-25933 (published 2023-05-18T22:15Z)

A type confusion bug in TypedArray prior to commit e6ed9c1a4b02dc219de1648f44cd808a56171b81 could have been used by a malicious attacker to execute arbitrary code via untrusted JavaScript. Note that this is only exploitable in cases where Hermes is used to execute untrusted JavaScript. Hence, most React Native applications are not affected.

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/facebook/hermes
commit_details: repo_url: https://github.com/facebook/hermes
author: Facebook Community Bot
author_date: 2023-04-05 06:59:33-07:00
msg: Re-sync with internal repository (#960)

Co-authored-by: Facebook Community Bot <6422482+facebook-github-bot@users.noreply.github.com>
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: HermesValue.h
change_type: ModificationType.MODIFY
diff: @@ -353,6 +353,26 @@ class HermesValue {
     return encodeDoubleValue((double)num);
   }
 
+  /// Encode a numeric value into the best possible representation based on the
+  /// static type of the parameter. Right now we only have one representation
+  /// (double), but that could change in the future. It cannot be guaranteed
+  /// that the NaN bits are all zeroes.
+  inline static HermesValue encodeUntrustedNumberValue(double num) {
+    return encodeUntrustedDoubleValue(num);
+  }
+
+  /// Encode a numeric value into the best possible representation based on the
+  /// static type of the parameter. Right now we only have one representation
+  /// (double), but that could change in the future. It cannot be guaranteed
+  /// that the NaN bits are all zeroes.
+  template <typename T>
+  inline static
+      typename std::enable_if<std::is_integral<T>::value, HermesValue>::type
+      encodeUntrustedNumberValue(T num) {
+    assert((double)num == num && "value not representable as double");
+    return encodeUntrustedDoubleValue((double)num);
+  }
+
   static HermesValue encodeNaNValue() {
     return HermesValue(
         llvh::DoubleToBits(std::numeric_limits<double>::quiet_NaN()));

programming_language: C++
filename: TypeInference.cpp
change_type: ModificationType.MODIFY
diff: @@ -60,6 +60,12 @@ class TypeInferenceImpl {
   bool runOnModule(Module *M);
 };
 
+/// \return if the given \p type is a BigInt|Object, which used to determine if
+/// unary/binary operations may have a BigInt result.
+static bool isBigIntOrObject(Type type) {
+  return type.canBeBigInt() || type.canBeObject();
+}
+
 static bool inferUnaryArith(UnaryOperatorInst *UOI, Type numberResultType) {
   Value *op = UOI->getSingleOperand();
 
@@ -73,10 +79,11 @@ static bool inferUnaryArith(UnaryOperatorInst *UOI, Type numberResultType) {
     return true;
   }
 
-  Type mayBeBigInt =
-      op->getType().canBeBigInt() ? Type::createBigInt() : Type::createNoType();
+  Type mayBeBigInt = isBigIntOrObject(op->getType()) ? Type::createBigInt()
+                                                     : Type::createNoType();
 
-  // - ?? => Number|?BigInt. BigInt is only possible if op.Type canBeBigInt.
+  // - ?? => Number|?BigInt. BigInt is only possible if op.Type is
+  // BigInt|Object.
   UOI->setType(Type::unionTy(numberResultType, mayBeBigInt));
   return true;
 }
@@ -298,12 +305,12 @@ static bool inferBinaryArith(
     return true;
   }
 
-  Type mayBeBigInt = LeftTy.canBeBigInt() && RightTy.canBeBigInt()
+  Type mayBeBigInt = (isBigIntOrObject(LeftTy) && isBigIntOrObject(RightTy))
       ? Type::createBigInt()
       : Type::createNoType();
 
-  // ?? - ?? => Number|?BigInt. BigInt is only possible if both operands can be
-  // BigInt due to the no automatic BigInt conversion.
+  // ?? - ?? => Number|?BigInt. BigInt is only possible if both operands are
+  // BigInt|Object due to the no automatic BigInt conversion.
   BOI->setType(Type::unionTy(numberType, mayBeBigInt));
   return true;
 }
@@ -312,12 +319,12 @@ static bool inferBinaryBitwise(BinaryOperatorInst *BOI) {
   Type LeftTy = BOI->getLeftHandSide()->getType();
   Type RightTy = BOI->getRightHandSide()->getType();
 
-  Type mayBeBigInt = LeftTy.canBeBigInt() && RightTy.canBeBigInt()
+  Type mayBeBigInt = (isBigIntOrObject(LeftTy) && isBigIntOrObject(RightTy))
       ? Type::createBigInt()
       : Type::createNoType();
 
-  // ?? - ?? => Int32|?BigInt. BigInt is only possible if both operands can be
-  // BigInt due to the no automatic BigInt conversion.
+  // ?? - ?? => Int32|?BigInt. BigInt is only possible if both operands are
+  // BigInt|Object due to the no automatic BigInt conversion.
   BOI->setType(Type::unionTy(Type::createInt32(), mayBeBigInt));
   return true;
 }
@@ -391,7 +398,7 @@ static bool inferBinaryInst(BinaryOperatorInst *BOI) {
       // ?BigInt + ?BigInt => ?BigInt. Both operands need to "may be a BigInt"
       // for a possible BigInt result from this operator. This is true because
       // there's no automative BigInt type conversion.
-      Type mayBeBigInt = (LeftTy.canBeBigInt() && RightTy.canBeBigInt())
+      Type mayBeBigInt = (isBigIntOrObject(LeftTy) && isBigIntOrObject(RightTy))
           ? Type::createBigInt()
           : Type::createNoType();
 

programming_language: C++
filename: TypedArray.cpp
change_type: ModificationType.MODIFY
diff: @@ -817,7 +817,7 @@ typedArrayPrototypeAt(void *, Runtime &runtime, NativeArgs args) {
     if (!arr->attached(runtime)) {                                         \
       return runtime.raiseTypeError("Underlying ArrayBuffer detached");    \
     }                                                                      \
-    return HermesValue::encodeNumberValue(arr->at(runtime, k));            \
+    return HermesValue::encodeUntrustedNumberValue(arr->at(runtime, k));   \
   }
   switch (O->getKind()) {
 #include "hermes/VM/TypedArrays.def"

programming_language: C++
filename: arguments.js
change_type: ModificationType.MODIFY
diff: @@ -43,7 +43,7 @@ function check_phi_handling(x) {
 // CHECK-NEXT:  $Reg2 @5 [empty]	%5 = StorePropertyInst %4 : closure, %2 : object, "select" : string
 // CHECK-NEXT:  $Reg2 @6 [7...8) 	%6 = HBCCreateFunctionInst %build#0#1()#4 : object, %0
 // CHECK-NEXT:  $Reg2 @7 [empty]	%7 = StorePropertyInst %6 : closure, %2 : object, "build" : string
-// CHECK-NEXT:  $Reg2 @8 [9...10) 	%8 = HBCCreateFunctionInst %buffalobuffalo#0#1()#5 : string|number, %0
+// CHECK-NEXT:  $Reg2 @8 [9...10) 	%8 = HBCCreateFunctionInst %buffalobuffalo#0#1()#5 : string|number|bigint, %0
 // CHECK-NEXT:  $Reg2 @9 [empty]	%9 = StorePropertyInst %8 : closure, %2 : object, "buffalobuffalo" : string
 // CHECK-NEXT:  $Reg0 @10 [11...12) 	%10 = HBCCreateFunctionInst %check_phi_handling#0#1()#6 : object, %0
 // CHECK-NEXT:  $Reg0 @11 [empty]	%11 = StorePropertyInst %10 : closure, %2 : object, "check_phi_handling" : string
@@ -85,7 +85,7 @@ function check_phi_handling(x) {
 // CHECK-NEXT:  $Reg0 @5 [empty]	%5 = ReturnInst %4
 // CHECK-NEXT:function_end
 
-// CHECK:function buffalobuffalo#0#1()#2 : string|number
+// CHECK:function buffalobuffalo#0#1()#2 : string|number|bigint
 // CHECK-NEXT:frame = []
 // CHECK-NEXT:%BB0:
 // CHECK-NEXT:  $Reg0 @0 [1...5) 	%0 = AllocStackInst $arguments
@@ -94,7 +94,7 @@ function check_phi_handling(x) {
 // CHECK-NEXT:  $Reg1 @3 [empty]	%3 = HBCReifyArgumentsInst %0
 // CHECK-NEXT:  $Reg0 @4 [5...6) 	%4 = LoadStackInst %0
 // CHECK-NEXT:  $Reg0 @5 [6...7) 	%5 = BinaryOperatorInst '+', %4, %4
-// CHECK-NEXT:  $Reg0 @6 [empty]	%6 = ReturnInst %5 : string|number
+// CHECK-NEXT:  $Reg0 @6 [empty]	%6 = ReturnInst %5 : string|number|bigint
 // CHECK-NEXT:function_end
 
 // CHECK:function check_phi_handling#0#1(x)#2 : object

programming_language: Objective-C
filename: type_infer.js
change_type: ModificationType.MODIFY
diff: @@ -92,7 +92,7 @@ function test_unary(x) {
 // CHECK-NEXT:  %18 = AllocObjectInst 0 : number, empty
 // CHECK-NEXT:  %19 = AllocObjectInst 0 : number, empty
 // CHECK-NEXT:  %20 = BinaryOperatorInst '+', %18 : object, %19 : object
-// CHECK-NEXT:  %21 = CallInst %x, undefined : undefined, %20 : string|number
+// CHECK-NEXT:  %21 = CallInst %x, undefined : undefined, %20 : string|number|bigint
 // CHECK-NEXT:  %22 = CallInst %x, undefined : undefined, NaN : number
 // CHECK-NEXT:  %23 = ReturnInst undefined : undefined
 // CHECK-NEXT:function_end

programming_language: Objective-C
filename: regress-bigint-type-inference.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,113 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+// RUN: %hermes -non-strict --dump-bytecode -O %s -fno-inline | %FileCheck %s --check-prefix=BC
+// RUN: %hermes -non-strict -O %s -fno-inline | %FileCheck %s --check-prefix=EXE
+
+function test() {
+    var o = { valueOf() { return 1n; } }
+    var bigint = 1n;
+    var result;
+
+    (() => result = o + bigint)();
+    print(result);
+    // BC: Add
+    // BC: StoreToEnvironment
+    // EXE: 2
+
+    (() => result = o - bigint)();
+    print(result);
+    // BC: Sub
+    // BC: StoreToEnvironment
+    // EXE: 0
+
+    (() => result = o * bigint)();
+    print(result);
+    // BC: Mul
+    // BC: StoreToEnvironment
+    // EXE: 1
+
+    (() => result = o / bigint)();
+    print(result);
+    // BC: Div
+    // BC: StoreToEnvironment
+    // EXE: 1
+
+    (() => result = o % bigint)();
+    print(result);
+    // BC: Mod
+    // BC: StoreToEnvironment
+    // EXE: 0
+
+    (() => result = o << bigint)();
+    print(result);
+    // BC: LShift
+    // BC: StoreToEnvironment
+    // EXE: 2
+
+    (() => result = o >> bigint)();
+    print(result);
+    // BC: RShift
+    // BC: StoreToEnvironment
+    // EXE: 0
+
+    (() => result = o | bigint)();
+    print(result);
+    // BC: BitOr
+    // BC: StoreToEnvironment
+    // EXE: 1
+
+    (() => result = o ^ bigint)();
+    print(result);
+    // BC: BitXor
+    // BC: StoreToEnvironment
+    // EXE: 0
+
+    (() => result = o & bigint)();
+    print(result);
+    // BC: BitAnd
+    // BC: StoreToEnvironment
+    // EXE: 1
+
+    (() => result = - bigint)();
+    print(result);
+    // BC: Negate
+    // BC: StoreToEnvironment
+    // EXE: -1
+
+    (() => result = ~ bigint)();
+    print(result);
+    // BC: BitNot
+    // BC: StoreToEnvironment
+    // EXE: -2
+
+    (() => result = o++)();
+    print(result);
+    // BC: Inc
+    // BC: StoreToEnvironment
+    // EXE: 1
+
+    (() => result = o--)();
+    print(result);
+    // BC: Dec
+    // BC: StoreToEnvironment
+    // EXE: 2
+
+    (() => result = ++o)();
+    print(result);
+    // BC: Inc
+    // BC: StoreToEnvironment
+    // EXE: 2
+
+    (() => result = --o)();
+    print(result);
+    // BC: Dec
+    // BC: StoreToEnvironment
+    // EXE: 1
+}
+
+test();

programming_language: C++
filename: regress-typed-array-at-nan.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,23 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+// RUN: %hermes -O -target=HBC %s | %FileCheck --match-full-lines %s
+// RUN: %hermes -O -target=HBC -emit-binary -out %t.hbc %s && %hermes %t.hbc | %FileCheck --match-full-lines %s
+// Test typed array will check for NaN when reading from an index.
+
+print("typed-array-at-nan");
+//CHECK-LABEL: typed-array-at-nan
+let ab = new ArrayBuffer(8);
+let i32a = new Int32Array(ab);
+
+i32a[0] = 0xcafebabe;
+i32a[1] = 0xffff0000;
+
+let f32a = new Float64Array(ab);
+var shouldBeNan = f32a.at(0)
+print(shouldBeNan);
+// CHECK: NaN

programming_language: Batchfile