CVE-2023-25933 (JavaScript, published 2023-05-18T22:15Z)
Description: value: 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.
Severity: nan (CVSS v3 base score: 9.8)
Weakness: CWE-843 \u2014 Access of Resource Using Incompatible Type ('Type Confusion')
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.
Repository: https://github.com/facebook/hermes
Files changed:
include/hermes/VM/HermesValue.h
lib/Optimizer/Scalar/TypeInference.cpp
lib/VM/JSLib/TypedArray.cpp
test/BCGen/HBC/arguments.js
test/Optimizer/type_infer.js
test/hermes/regress-bigint-type-inference.js
test/hermes/regress-typed-array-at-nan.js
Commit message: Re-sync with internal repository (#960)

Co-authored-by: Facebook Community Bot <6422482+facebook-github-bot@users.noreply.github.com>

Vulnerable code:
  Type mayBeBigInt =
      op->getType().canBeBigInt() ? Type::createBigInt() : Type::createNoType();
  // - ?? => Number|?BigInt. BigInt is only possible if op.Type canBeBigInt.
  Type mayBeBigInt = LeftTy.canBeBigInt() && RightTy.canBeBigInt()
  // ?? - ?? => Number|?BigInt. BigInt is only possible if both operands can be
  // BigInt due to the no automatic BigInt conversion.
  Type mayBeBigInt = LeftTy.canBeBigInt() && RightTy.canBeBigInt()
  // ?? - ?? => Int32|?BigInt. BigInt is only possible if both operands can be
  // BigInt due to the no automatic BigInt conversion.
      Type mayBeBigInt = (LeftTy.canBeBigInt() && RightTy.canBeBigInt())
    return HermesValue::encodeNumberValue(arr->at(runtime, k));            \
// CHECK-NEXT:  $Reg2 @8 [9...10) 	%8 = HBCCreateFunctionInst %buffalobuffalo#0#1()#5 : string|number, %0
// CHECK:function buffalobuffalo#0#1()#2 : string|number
// CHECK-NEXT:  $Reg0 @6 [empty]	%6 = ReturnInst %5 : string|number
// CHECK-NEXT:  %21 = CallInst %x, undefined : undefined, %20 : string|number

Fixed code:
  /// 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);
  }

/// \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();
}

  Type mayBeBigInt = isBigIntOrObject(op->getType()) ? Type::createBigInt()
                                                     : Type::createNoType();
  // - ?? => Number|?BigInt. BigInt is only possible if op.Type is
  // BigInt|Object.
  Type mayBeBigInt = (isBigIntOrObject(LeftTy) && isBigIntOrObject(RightTy))
  // ?? - ?? => Number|?BigInt. BigInt is only possible if both operands are
  // BigInt|Object due to the no automatic BigInt conversion.
  Type mayBeBigInt = (isBigIntOrObject(LeftTy) && isBigIntOrObject(RightTy))
  // ?? - ?? => Int32|?BigInt. BigInt is only possible if both operands are
  // BigInt|Object due to the no automatic BigInt conversion.
      Type mayBeBigInt = (isBigIntOrObject(LeftTy) && isBigIntOrObject(RightTy))
    return HermesValue::encodeUntrustedNumberValue(arr->at(runtime, k));   \
// CHECK-NEXT:  $Reg2 @8 [9...10) 	%8 = HBCCreateFunctionInst %buffalobuffalo#0#1()#5 : string|number|bigint, %0
// CHECK:function buffalobuffalo#0#1()#2 : string|number|bigint
// CHECK-NEXT:  $Reg0 @6 [empty]	%6 = ReturnInst %5 : string|number|bigint
// CHECK-NEXT:  %21 = CallInst %x, undefined : undefined, %20 : string|number|bigint
/**
 * 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();
/**
 * 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