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

A bytecode optimization bug in Hermes prior to commit e6ed9c1a4b02dc219de1648f44cd808a56171b81 could be used to cause an use-after-free and obtain arbitrary code execution via a carefully crafted payload. 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-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/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