CVE-2022-40138 (published 2022-10-11T02:15Z)

An integer conversion error in Hermes bytecode generation, prior to commit 6aa825e480d48127b480b08d13adf70033237097, could have been used to perform Out-Of-Bounds operations and subsequently execute arbitrary code. 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-681
cwe_name: Incorrect Conversion between Numeric Types
description: When converting from one data type to another, such as long to integer, data can be omitted or translated in a way that produces unexpected values. If the resulting values are used in a sensitive context, then dangerous behaviors may occur.
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: 2022-09-27 11:14:30-04:00
msg: Re-sync with internal repository (#822)

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: BytecodeInstructionGenerator.h
change_type: ModificationType.MODIFY
diff: @@ -45,7 +45,16 @@ class BytecodeInstructionGenerator {
   /// A list of opcodes.
   std::vector<opcode_atom_t> opcodes_{};
 
+  /// A flag indicating that an encoding error happened, in which case the
+  /// bytecode is wrong and shouldn't be executed.
+  bool encodingError_{};
+
  public:
+  /// Returns whether an encoding error happened during bytecode emission.
+  bool hasEncodingError() const {
+    return encodingError_;
+  }
+
   /// Returns the current location of the bytecode stream.
   offset_t getCurrentLocation() {
     return opcodes_.size();
@@ -76,13 +85,11 @@ class BytecodeInstructionGenerator {
 // We also assert that the value can fit into ctype.
 // For integer values, ((param_t)(ctype)value) == value will do the job;
 // We also want doubles to pass the check unconditionally.
-#define DEFINE_OPERAND_TYPE(name, ctype)          \
-  void emit##name(param_t value) {                \
-    assert(                                       \
-        (((param_t)(ctype)value) == value ||      \
-         std::is_floating_point<ctype>::value) && \
-        "Value does not fit in " #ctype);         \
-    emitOperand(value, sizeof(ctype));            \
+#define DEFINE_OPERAND_TYPE(name, ctype)                  \
+  void emit##name(param_t value) {                        \
+    encodingError_ |= ((param_t)(ctype)value) != value && \
+        !std::is_floating_point<ctype>::value;            \
+    emitOperand(value, sizeof(ctype));                    \
   }
 #include "hermes/BCGen/HBC/BytecodeList.def"
 

programming_language: C++
filename: CompilerDriver.h
change_type: ModificationType.MODIFY
diff: @@ -32,6 +32,8 @@ enum CompileStatus {
   OutputFileError,
   /// An error occured during optimization.
   OptimizationFailed,
+  /// An error occured in the backend during/after IR lowering.
+  BackendError,
 };
 
 /// Information about a bytecode file that is loaded into a buffer.

programming_language: C++
filename: CodeBlock.h
change_type: ModificationType.MODIFY
diff: @@ -63,7 +63,7 @@ class CodeBlock final
 
 #ifndef HERMESVM_LEAN
   /// Compiles a lazy CodeBlock. Intended to be called from lazyCompile.
-  void lazyCompileImpl(Runtime &runtime);
+  ExecutionStatus lazyCompileImpl(Runtime &runtime);
 #endif
 
   /// Helper function for getting start and end locations.
@@ -237,17 +237,20 @@ class CodeBlock final
   }
 
   /// Compiles this CodeBlock, if it's lazy and not already compiled.
-  void lazyCompile(Runtime &runtime) {
+  ExecutionStatus lazyCompile(Runtime &runtime) {
     if (LLVM_UNLIKELY(isLazy())) {
-      lazyCompileImpl(runtime);
+      return lazyCompileImpl(runtime);
     }
+    return ExecutionStatus::RETURNED;
   }
 #else
   /// Checks whether this function is lazily compiled.
   bool isLazy() const {
     return false;
   }
-  void lazyCompile(Runtime &) {}
+  ExecutionStatus lazyCompile(Runtime &) {
+    return ExecutionStatus::RETURNED;
+  }
 #endif
 
   /// Get the start location of this function, if it's lazy.

programming_language: C++
filename: BytecodeGenerator.cpp
change_type: ModificationType.MODIFY
diff: @@ -217,6 +217,8 @@ void BytecodeModuleGenerator::setFunctionGenerator(
   assert(
       functionGenerators_.find(F) == functionGenerators_.end() &&
       "Adding same function twice.");
+  assert(
+      !BFG->hasEncodingError() && "Error should have been reported already.");
   functionGenerators_[F] = std::move(BFG);
 }
 

programming_language: C++
filename: BytecodeProviderFromSrc.cpp
change_type: ModificationType.MODIFY
diff: @@ -236,8 +236,11 @@ BCProviderFromSrc::createBCProviderFromSrcImpl(
   opts.staticBuiltinsEnabled =
       context->getOptimizationSettings().staticBuiltins;
   opts.verifyIR = compileFlags.verifyIR;
-  auto bytecode = createBCProviderFromSrc(
-      hbc::generateBytecodeModule(&M, M.getTopLevelFunction(), opts));
+  auto BM = hbc::generateBytecodeModule(&M, M.getTopLevelFunction(), opts);
+  if (context->getSourceErrorManager().getErrorCount() > 0) {
+    return {nullptr, getErrorString()};
+  }
+  auto bytecode = createBCProviderFromSrc(std::move(BM));
   bytecode->singleFunction_ = isSingleFunctionExpression(parsed.getValue());
   return {std::move(bytecode), std::string{}};
 }

programming_language: C++
filename: HBC.cpp
change_type: ModificationType.MODIFY
diff: @@ -313,6 +313,11 @@ std::unique_ptr<BytecodeModule> hbc::generateBytecodeModule(
       debugCache = hbciSel.getDebugCache();
     }
 
+    if (funcGen->hasEncodingError()) {
+      M->getContext().getSourceErrorManager().error(
+          F.getSourceRange().Start, "Error encoding bytecode");
+      return nullptr;
+    }
     BMGen.setFunctionGenerator(&F, std::move(funcGen));
   }
 
@@ -335,6 +340,10 @@ std::unique_ptr<BytecodeModule> hbc::generateBytecode(
       sourceMapGen,
       std::move(baseBCProvider));
 
+  if (!BM) {
+    return {};
+  }
+
   if (options.format == OutputFormatKind::EmitBundle) {
     assert(BM != nullptr);
     BytecodeSerializer BS{OS, options};

programming_language: C++
filename: ISel.cpp
change_type: ModificationType.MODIFY
diff: @@ -1337,6 +1337,10 @@ void HBCISel::generateHBCResolveEnvironment(
       "Cannot access variables in inner scopes");
   int32_t delta = curScopeDepth.getValue() - instScopeDepth.getValue();
   assert(delta > 0 && "HBCResolveEnvironment for current scope");
+  if (std::numeric_limits<uint8_t>::max() < delta) {
+    F_->getContext().getSourceErrorManager().error(
+        Inst->getLocation(), "Variable environment is out-of-reach");
+  }
   BCFGen_->emitGetEnvironment(encodeValue(Inst), delta - 1);
 }
 void HBCISel::generateHBCStoreToEnvironmentInst(

programming_language: C++
filename: CompilerDriver.cpp
change_type: ModificationType.MODIFY
diff: @@ -1694,9 +1694,15 @@ CompileResult generateBytecodeForExecution(
   std::shared_ptr<Context> context = M.shareContext();
   CompileResult result{Success};
   if (cl::BytecodeFormat == cl::BytecodeFormatKind::HBC) {
-    result.bytecodeProvider = hbc::BCProviderFromSrc::createBCProviderFromSrc(
-        hbc::generateBytecodeModule(&M, M.getTopLevelFunction(), genOptions));
+    auto BM =
+        hbc::generateBytecodeModule(&M, M.getTopLevelFunction(), genOptions);
+    if (auto N = context->getSourceErrorManager().getErrorCount()) {
+      llvh::errs() << "Emitted " << N << " errors in the backend. exiting.\n";
+      return BackendError;
+    }
 
+    result.bytecodeProvider =
+        hbc::BCProviderFromSrc::createBCProviderFromSrc(std::move(BM));
   } else {
     llvm_unreachable("Invalid bytecode kind for execution");
     result = InvalidFlags;
@@ -1735,6 +1741,11 @@ CompileResult generateBytecodeForSerialization(
         sourceMapGenOrNull,
         std::move(baseBCProvider));
 
+    if (auto N = M.getContext().getSourceErrorManager().getErrorCount()) {
+      llvh::errs() << "Emitted " << N << " errors in the backend. exiting.\n";
+      return BackendError;
+    }
+
     if (cl::DumpTarget == DumpBytecode) {
       disassembleBytecode(hbc::BCProviderFromSrc::createBCProviderFromSrc(
           std::move(bytecodeModule)));

programming_language: C++
filename: Callable.cpp
change_type: ModificationType.MODIFY
diff: @@ -1395,7 +1395,11 @@ CallResult<PseudoHandle<>> GeneratorInnerFunction::callInnerFunction(
   // Note that this will do nothing after the very first time a lazy function
   // is called, so we only resize before we save any registers at all.
   if (LLVM_UNLIKELY(selfHandle->getCodeBlock(runtime)->isLazy())) {
-    selfHandle->getCodeBlock(runtime)->lazyCompile(runtime);
+    if (LLVM_UNLIKELY(
+            selfHandle->getCodeBlock(runtime)->lazyCompile(runtime) ==
+            ExecutionStatus::EXCEPTION)) {
+      return ExecutionStatus::EXCEPTION;
+    }
     if (LLVM_UNLIKELY(
             ArrayStorage::resize(
                 ctx,

programming_language: C++
filename: CodeBlock.cpp
change_type: ModificationType.MODIFY
diff: @@ -14,6 +14,7 @@
 #include "hermes/IRGen/IRGen.h"
 #include "hermes/Support/Conversions.h"
 #include "hermes/Support/PerfSection.h"
+#include "hermes/Support/SimpleDiagHandler.h"
 #include "hermes/VM/GCPointer-inline.h"
 #include "hermes/VM/Runtime.h"
 #include "hermes/VM/RuntimeModule.h"
@@ -330,14 +331,24 @@ std::unique_ptr<hbc::BytecodeModule> compileLazyFunction(
 }
 } // namespace
 
-void CodeBlock::lazyCompileImpl(Runtime &runtime) {
+ExecutionStatus CodeBlock::lazyCompileImpl(Runtime &runtime) {
   assert(isLazy() && "Laziness has not been checked");
   PerfSection perf("Lazy function compilation");
   auto *provider = (hbc::BCProviderLazy *)runtimeModule_->getBytecode();
   auto *func = provider->getBytecodeFunction();
   auto *lazyData = func->getLazyCompilationData();
+  SourceErrorManager &manager = lazyData->context->getSourceErrorManager();
+  SimpleDiagHandlerRAII outputManager{manager};
   auto bcModule = compileLazyFunction(lazyData);
 
+  if (manager.getErrorCount()) {
+    // Raise a SyntaxError to be consistent with eval().
+    return runtime.raiseSyntaxError(
+        llvh::StringRef{outputManager.getErrorString()});
+  }
+
+  assert(bcModule && "No errors, yet no bcModule");
+
   runtimeModule_->initializeLazyMayAllocate(
       hbc::BCProviderFromSrc::createBCProviderFromSrc(std::move(bcModule)));
   // Reset all meta lazyData of the CodeBlock to point to the newly
@@ -346,6 +357,8 @@ void CodeBlock::lazyCompileImpl(Runtime &runtime) {
   functionHeader_ =
       runtimeModule_->getBytecode()->getFunctionHeader(functionID_);
   bytecode_ = runtimeModule_->getBytecode()->getBytecode(functionID_);
+
+  return ExecutionStatus::RETURNED;
 }
 #endif // HERMESVM_LEAN
 

programming_language: C++
filename: Debugger.cpp
change_type: ModificationType.MODIFY
diff: @@ -1147,7 +1147,12 @@ bool Debugger::resolveBreakpointLocation(Breakpoint &breakpoint) const {
            (start.col <= request.column && request.column <= end.col))) {
         // The code block probably contains the breakpoint we want to set.
         // First, we compile it.
-        codeBlock->lazyCompile(runtime_);
+        if (LLVM_UNLIKELY(
+                codeBlock->lazyCompile(runtime_) ==
+                ExecutionStatus::EXCEPTION)) {
+          // TODO: how to better handle this?
+          runtime_.clearThrownValue();
+        }
 
         // We've found the codeBlock at this level and expanded it,
         // so there's no point continuing the search.

programming_language: C++
filename: Interpreter.cpp
change_type: ModificationType.MODIFY
diff: @@ -785,7 +785,10 @@ static inline const Inst *nextInstCall(const Inst *ip) {
 
 CallResult<HermesValue> Runtime::interpretFunctionImpl(
     CodeBlock *newCodeBlock) {
-  newCodeBlock->lazyCompile(*this);
+  if (LLVM_UNLIKELY(
+          newCodeBlock->lazyCompile(*this) == ExecutionStatus::EXCEPTION)) {
+    return ExecutionStatus::EXCEPTION;
+  }
 
 #if defined(HERMES_MEMORY_INSTRUMENTATION) || !defined(NDEBUG)
   // We always call getCurrentIP() in a debug build as this has the effect
@@ -1584,7 +1587,10 @@ tailCall:
 #endif
 
         CodeBlock *calleeBlock = func->getCodeBlock(runtime);
-        CAPTURE_IP(calleeBlock->lazyCompile(runtime));
+        CAPTURE_IP_ASSIGN(auto res, calleeBlock->lazyCompile(runtime));
+        if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) {
+          goto exception;
+        }
         curCodeBlock = calleeBlock;
         CAPTURE_IP_SET();
         goto tailCall;
@@ -1637,7 +1643,10 @@ tailCall:
 
         assert(!SingleStep && "can't single-step a call");
 
-        CAPTURE_IP(calleeBlock->lazyCompile(runtime));
+        CAPTURE_IP_ASSIGN(auto res, calleeBlock->lazyCompile(runtime));
+        if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) {
+          goto exception;
+        }
         curCodeBlock = calleeBlock;
         CAPTURE_IP_SET();
         goto tailCall;

programming_language: C++
filename: Operations.cpp
change_type: ModificationType.MODIFY
diff: @@ -1679,7 +1679,9 @@ CallResult<bool> isConstructor(Runtime &runtime, Callable *callable) {
     auto *cb = func->getCodeBlock(runtime);
     // Even though it doesn't make sense logically, we need to compile the
     // function in order to access it flags.
-    cb->lazyCompile(runtime);
+    if (LLVM_UNLIKELY(cb->lazyCompile(runtime) == ExecutionStatus::EXCEPTION)) {
+      return ExecutionStatus::EXCEPTION;
+    }
     return !func->getCodeBlock(runtime)->getHeaderFlags().isCallProhibited(
         true);
   }

programming_language: C++
filename: far-environment-access-eval.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,33 @@
+/**
+ * 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 2>&1 | %FileCheck --match-full-lines %s
+// RUN: %hermes -O -target=HBC %s -lazy 2>&1 | %FileCheck --match-full-lines %s
+// XFAIL: windows
+// UNSUPPORTED: ubsan
+
+// Hermes has a 256 limit on the number of scopes nesting. This test ensures
+// that the compiler will handle applications that require more scopes
+// gracefully -- i.e., it won't crash, nor emit bad bytecode.
+
+try {
+    eval('"use strict";\n' +
+         '\n' +
+         'function sink(x) { return x; }\n' +
+         'function foo(){\n' +
+         '  var var0 = sink("1");\n' +
+         '  var var1 = sink("2");\n' +
+         '  var var2 = sink("3");\n' +
+         '  return ()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=> [var0, var1, var2];\n' +
+         '}\n' +
+         '\n' +
+         'print(foo()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()());\n')
+} catch (e) {
+  print(e);
+}
+
+// CHECK: SyntaxError: {{[0-9]+}}:{{[0-9]+}}:Variable environment is out-of-reach

programming_language: TypeScript
filename: far-environment-access.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,36 @@
+/**
+ * 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: (%hermesc -O -target=HBC %s --dump-bytecode 2>&1 || true) | %FileCheck --match-full-lines %s
+// RUN: (%hermes -O -target=HBC %s 2>&1 || true) | %FileCheck --match-full-lines %s
+// RUN: %hermes -O -target=HBC %s -lazy 2>&1 | %FileCheck --match-full-lines %s --check-prefix=LAZY
+// XFAIL: windows
+// UNSUPPORTED: ubsan
+
+// Hermes has a 256 limit on the number of scopes nesting. This test ensures
+// that the compiler will handle applications that require more scopes
+// gracefully -- i.e., it won't crash, nor emit bad bytecode.
+
+"use strict";
+
+function sink(x) { return x; }
+function foo(){
+  var var0 = sink("1");
+  var var1 = sink("2");
+  var var2 = sink("3");
+  return ()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=>()=> [var0, var1, var2];
+}
+
+try {
+  print(foo()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()());
+} catch (e) {
+  print(e);
+}
+
+
+// CHECK: {{.*}}far-environment-access.js:{{[0-9]+}}:{{[0-9]+}}: error: Variable environment is out-of-reach
+// LAZY: SyntaxError: {{[0-9]+}}:{{[0-9]+}}:Variable environment is out-of-reach

programming_language: TypeScript