CVE-2023-28081 (JavaScript, published 2023-05-18T22:15Z)
Description: value: 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.
Severity: nan (CVSS v3 base score: 9.8)
Weakness: CWE-416 \u2014 Use After Free
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.
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