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

A null pointer dereference bug in Hermes prior to commit 5cae9f72975cf0e5a62b27fdd8b01f103e198708 could have been used by an attacker to crash an Hermes runtime where the EnableHermesInternal config option was set to true. 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: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)

Weakness information:
cwe_id: CWE-476
cwe_name: NULL Pointer Dereference
description: The product dereferences a pointer that it expects to be valid but is NULL.
extended_description: 
            
            
            
         
         

Fix information:
repo_url: https://github.com/facebook/hermes
commit_details: repo_url: https://github.com/facebook/hermes
author: John Porto
author_date: 2023-01-31 10:23:52-08:00
msg: Avoid accessing the DebugInfo for BCProviderLazy

Summary: Fixes CVE-2023-24832 by avoiding accessing the DebugInfo for lazy CodeBlocks.

Reviewed By: mattbfb

Differential Revision: D42688370

fbshipit-source-id: fc7eb4168b1f21549cbfd080a9e010c6d8609256
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: BytecodeProviderFromSrc.h
change_type: ModificationType.MODIFY
diff: @@ -219,7 +219,7 @@ class BCProviderLazy final : public BCProviderBase {
 
   /// No debug information will be available without compiling it.
   void createDebugInfo() override {
-    llvm_unreachable("Accessing debug info from a lazy module");
+    hermes_fatal("Accessing debug info from a lazy module");
   }
 
  public:
@@ -234,20 +234,20 @@ class BCProviderLazy final : public BCProviderBase {
   }
 
   StringTableEntry getStringTableEntry(uint32_t index) const override {
-    llvm_unreachable("Accessing string table from a lazy module");
+    hermes_fatal("Accessing string table from a lazy module");
   }
 
   const uint8_t *getBytecode(uint32_t) const override {
-    llvm_unreachable("Accessing bytecode from a lazy module");
+    hermes_fatal("Accessing bytecode from a lazy module");
   }
 
   llvh::ArrayRef<hbc::HBCExceptionHandlerInfo> getExceptionTable(
       uint32_t) const override {
-    llvm_unreachable("Accessing exception info from a lazy module");
+    hermes_fatal("Accessing exception info from a lazy module");
   }
 
   const hbc::DebugOffsets *getDebugOffsets(uint32_t) const override {
-    llvm_unreachable("Accessing debug offsets from a lazy module");
+    hermes_fatal("Accessing debug offsets from a lazy module");
   }
 
   bool isFunctionLazy(uint32_t) const override {

programming_language: C++
filename: HermesInternal.cpp
change_type: ModificationType.MODIFY
diff: @@ -574,14 +574,18 @@ static CallResult<HermesValue> getCodeBlockFileName(
     const CodeBlock *codeBlock,
     OptValue<hbc::DebugSourceLocation> location) {
   RuntimeModule *runtimeModule = codeBlock->getRuntimeModule();
-  if (location) {
-    auto debugInfo = runtimeModule->getBytecode()->getDebugInfo();
-    return StringPrimitive::createEfficient(
-        runtime, debugInfo->getFilenameByID(location->filenameId));
-  } else {
-    llvh::StringRef sourceURL = runtimeModule->getSourceURL();
-    if (!sourceURL.empty()) {
-      return StringPrimitive::createEfficient(runtime, sourceURL);
+  if (!runtimeModule->getBytecode()->isLazy()) {
+    // Lazy code blocks do not have debug information (and will hermes_fatal if
+    // you try to access it), so only touch it for non-lazy blocks.
+    if (location) {
+      auto debugInfo = runtimeModule->getBytecode()->getDebugInfo();
+      return StringPrimitive::createEfficient(
+          runtime, debugInfo->getFilenameByID(location->filenameId));
+    } else {
+      llvh::StringRef sourceURL = runtimeModule->getSourceURL();
+      if (!sourceURL.empty()) {
+        return StringPrimitive::createEfficient(runtime, sourceURL);
+      }
     }
   }
   return HermesValue::encodeUndefinedValue();
@@ -803,6 +807,24 @@ hermesInternalFuzzilli(void *, Runtime &runtime, NativeArgs args) {
 }
 #endif // HERMES_ENABLE_FUZZILLI
 
+static CallResult<HermesValue>
+hermesInternalIsLazy(void *, Runtime &runtime, NativeArgs args) {
+  auto callable = args.dyncastArg<Callable>(0);
+  if (!callable) {
+    return HermesValue::encodeBoolValue(false);
+  }
+
+  auto codeBlock = getLeafCodeBlock(callable, runtime);
+  if (!codeBlock) {
+    // Native function is never lazy.
+    return HermesValue::encodeBoolValue(false);
+  }
+
+  RuntimeModule *runtimeModule = codeBlock->getRuntimeModule();
+  return HermesValue::encodeBoolValue(
+      runtimeModule && runtimeModule->getBytecode()->isLazy());
+}
+
 Handle<JSObject> createHermesInternalObject(
     Runtime &runtime,
     const JSLibFlags &flags) {
@@ -912,6 +934,7 @@ Handle<JSObject> createHermesInternalObject(
     defineInternMethod(
         P::copyDataProperties, hermesBuiltinCopyDataProperties, 3);
     defineInternMethodAndSymbol("isProxy", hermesInternalIsProxy);
+    defineInternMethodAndSymbol("isLazy", hermesInternalIsLazy);
     defineInternMethod(P::drainJobs, hermesInternalDrainJobs);
   }
 

programming_language: C++
filename: regress-hermes-internal-get-lazy-function-location.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,35 @@
+/**
+ * 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 -g --lazy --Xhermes-internal-test-methods -O %s | %FileCheck %s
+// RUN: %hermes -g --lazy --Xhermes-internal-test-methods -O0 %s | %FileCheck %s
+
+"use strict";
+
+// isLazy should be able to handle native functions.
+print(HermesInternal.isLazy(print));
+// CHECK: false
+
+// isLazy should be able to handle non-callables.
+print(HermesInternal.isLazy(10));
+// CHECK: false
+
+function lazyFunc() {}
+
+print(HermesInternal.isLazy(lazyFunc));
+// CHECK: true
+
+print(JSON.stringify(HermesInternal.getFunctionLocation(lazyFunc)));
+// CHECK: {"isNative":false,"lineNumber":21,"columnNumber":1}
+
+lazyFunc();
+
+print(HermesInternal.isLazy(lazyFunc));
+// CHECK: false
+
+print(JSON.stringify(HermesInternal.getFunctionLocation(lazyFunc)));
+// CHECK: {"isNative":false,"lineNumber":21,"columnNumber":1,"fileName":"{{.*}}regress-hermes-internal-get-lazy-function-location.js"}

programming_language: Go