CVE-2023-24832: NULL Pointer Dereference (CWE-476)
value: 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.
CWE description: The product dereferences a pointer that it expects to be valid but is NULL.
CVSS v3 base score: 7.5
Published: 2023-05-18T22:15Z
Repository: https://github.com/facebook/hermes
Language: C++
Files changed: include/hermes/BCGen/HBC/BytecodeProviderFromSrc.h, lib/VM/JSLib/HermesInternal.cpp, test/hermes/regress-hermes-internal-get-lazy-function-location.js
Commit date: 2023-01-31 10:23:52 -0800
Commit message: 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

Diff: diff --git a/include/hermes/BCGen/HBC/BytecodeProviderFromSrc.h b/include/hermes/BCGen/HBC/BytecodeProviderFromSrc.h
index 070af43dcf..5a5020ff69 100644
--- a/include/hermes/BCGen/HBC/BytecodeProviderFromSrc.h
+++ b/include/hermes/BCGen/HBC/BytecodeProviderFromSrc.h
@@ -217,11 +217,11 @@ class BCProviderLazy final : public BCProviderBase {
 
   explicit BCProviderLazy(hbc::BytecodeFunction *bytecodeFunction);
 
   /// 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:
   static std::unique_ptr<BCProviderBase> createBCProviderLazy(
       hbc::BytecodeFunction *bytecodeFunction) {
@@ -232,24 +232,24 @@ class BCProviderLazy final : public BCProviderBase {
   RuntimeFunctionHeader getFunctionHeader(uint32_t) const override {
     return RuntimeFunctionHeader(&bytecodeFunction_->getHeader());
   }
 
   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 {
     return true;
   }
diff --git a/lib/VM/JSLib/HermesInternal.cpp b/lib/VM/JSLib/HermesInternal.cpp
index d957660cd7..3d8c308a62 100644
--- a/lib/VM/JSLib/HermesInternal.cpp
+++ b/lib/VM/JSLib/HermesInternal.cpp
@@ -572,18 +572,22 @@ static const CodeBlock *getLeafCodeBlock(
 static CallResult<HermesValue> getCodeBlockFileName(
     Runtime &runtime,
     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();
 }
 
@@ -801,10 +805,28 @@ hermesInternalFuzzilli(void *, Runtime &runtime, NativeArgs args) {
 
   return HermesValue::encodeUndefinedValue();
 }
 #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) {
   namespace P = Predefined;
   Handle<JSObject> intern = runtime.makeHandle(JSObject::create(runtime));
@@ -910,10 +932,11 @@ Handle<JSObject> createHermesInternalObject(
         P::detachArrayBuffer, hermesInternalDetachArrayBuffer, 1);
     defineInternMethod(P::getWeakSize, hermesInternalGetWeakSize);
     defineInternMethod(
         P::copyDataProperties, hermesBuiltinCopyDataProperties, 3);
     defineInternMethodAndSymbol("isProxy", hermesInternalIsProxy);
+    defineInternMethodAndSymbol("isLazy", hermesInternalIsLazy);
     defineInternMethod(P::drainJobs, hermesInternalDrainJobs);
   }
 
 #ifdef HERMESVM_EXCEPTION_ON_OOM
   defineInternMethodAndSymbol("getCallStack", hermesInternalGetCallStack, 0);
diff --git a/test/hermes/regress-hermes-internal-get-lazy-function-location.js b/test/hermes/regress-hermes-internal-get-lazy-function-location.js
new file mode 100644
index 0000000000..3507d51c3d
--- /dev/null
+++ b/test/hermes/regress-hermes-internal-get-lazy-function-location.js
@@ -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"}

Vulnerable code:     llvm_unreachable("Accessing debug info from a lazy module");
    llvm_unreachable("Accessing string table from a lazy module");
    llvm_unreachable("Accessing bytecode from a lazy module");
    llvm_unreachable("Accessing exception info from a lazy module");
    llvm_unreachable("Accessing debug offsets from a lazy module");
  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);
Fixed code:     hermes_fatal("Accessing debug info from a lazy module");
    hermes_fatal("Accessing string table from a lazy module");
    hermes_fatal("Accessing bytecode from a lazy module");
    hermes_fatal("Accessing exception info from a lazy module");
    hermes_fatal("Accessing debug offsets from a lazy module");
  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);
      }
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());
}

    defineInternMethodAndSymbol("isLazy", hermesInternalIsLazy);
/**
 * 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"}