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

A use-after-free related to unsound inference in the bytecode generation when optimizations are enabled for Hermes prior to commit da8990f737ebb9d9810633502f65ed462b819c09 could have been used by an attacker to achieve remote code execution. 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-416
cwe_name: Use After Free
description: 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.
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-04-07 13:31:05-07:00
msg: Treat usages of arguments.callee as unknown callsites

Summary:
arguments.callee in non-strict mode are indirect references to the current function. As such,
make sure that call site inference bails if there's any possibility such a reference is made. This
is inexact by nature, so the analysis needs to be conservative.

nocommit until a solution is decided.

Reviewed By: neildhar

Differential Revision: D44674327

fbshipit-source-id: c118b23d91ffe872e65dea0560b3c622802c2eef
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: SimpleCallGraphProvider.cpp
change_type: ModificationType.MODIFY
diff: @@ -66,6 +66,13 @@ static bool identifyCallees(CallInst *CI, llvh::DenseSet<Function *> &callees) {
 static bool identifyCallsites(
     Function *F,
     llvh::DenseSet<CallInst *> &callSites) {
+  // Non-strict functions can be aliased in multiple ways (
+  // arguments.callee, Function.prototype.caller etc). Do not try to identify
+  // the callsites for those.
+  if (!F->isStrictMode()) {
+    return false;
+  }
+
   for (auto *CU : F->getUsers()) {
     if (auto *CI = llvh::dyn_cast<CallInst>(CU)) {
       if (!isDirectCallee(F, CI))

programming_language: C++
filename: non-strict-opts.js
change_type: ModificationType.MODIFY
diff: @@ -39,7 +39,7 @@ function main()  {
 // CHECK-NEXT:  %3 = ReturnInst undefined : undefined
 // CHECK-NEXT:function_end
 
-// CHECK:function foo#1#2(p1 : number)#3 : string
+// CHECK:function foo#1#2(p1)#3 : string
 // CHECK-NEXT:S{foo#1#2()#3} = []
 // CHECK-NEXT:%BB0:
 // CHECK-NEXT:  %0 = CreateScopeInst %S{foo#1#2()#3}

programming_language: Objective-C
filename: regress-arguments-callee.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,133 @@
+/**
+ * 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 %s
+
+var f;
+var funcp;
+var argumentsp;
+var callee = _ => "callee";
+
+function calleeDirectly(a) {
+    return a.callee;
+}
+
+function calleeIndirectly(a) {
+    return a[callee()];
+}
+
+print("directArgumentsCallee");
+(function directArgumentsCallee(param) {
+    try {
+        funcp = arguments.callee;
+    } catch (e) {
+    }
+    let f = () => {
+        return param;
+    };
+    return f;
+})();
+f = funcp({ a: 1.1 });
+f();
+
+print("indirectArgumentsCallee");
+(function indirectArgumentsCallee(param) {
+    try {
+        funcp = arguments.callee;
+    } catch (e) {
+    }
+    let f = () => {
+        return param;
+    };
+    return f;
+})();
+f = funcp({ a: 1.1 });
+f();
+
+print("passignArgumentsToAnotherFunction_directArgumentsCallee");
+(function passignArgumentsToAnotherFunction_DirectArgumentsCallee(param) {
+    try {
+        funcp = calleeDirectly(arguments);
+    } catch (e) {
+    }
+    let f = () => {
+        return param;
+    };
+    return f;
+})();
+f = funcp({ a: 1.1 });
+f();
+
+print("passignArgumentsToAnotherFunction_indirectArgumentsCallee");
+(function passignArgumentsToAnotherFunction_indirectArgumentsCallee(param) {
+    try {
+        funcp = calleeIndirectly(arguments);
+    } catch (e) {
+    }
+    let f = () => {
+        return param;
+    };
+    return f;
+})();
+f = funcp({ a: 1.1 });
+f();
+
+print("arrow_directArgumentsCallee");
+(function arrow_directArgumentsCallee(param) {
+    try {
+        funcp = (_ => arguments.callee)();
+    } catch (e) {
+    }
+    let f = () => {
+        return param;
+    };
+    return f;
+})();
+f = funcp({ a: 1.1 });
+f();
+
+print("arrow_indirectArgumentsCallee");
+(function arrow_indirectArgumentsCallee(param) {
+    try {
+        funcp = (_ => arguments[callee()])();
+    } catch (e) {
+    }
+    let f = () => {
+        return param;
+    };
+    return f;
+})();
+f = funcp({ a: 1.1 });
+f();
+
+print("capture_directArgumentsCallee");
+(function capture_directArgumentsCallee(param) {
+    try {
+        argumentsp = arguments;
+    } catch (e) {
+    }
+    let f = () => {
+        return param;
+    };
+    return f;
+})();
+f = argumentsp.callee({ a: 1.1 });
+f();
+
+print("capture_indirectArgumentsCallee");
+(function capture_indirectArgumentsCallee(param) {
+    try {
+        argumentsp = arguments;
+    } catch (e) {
+    }
+    let f = () => {
+        return param;
+    };
+    return f;
+})();
+f = argumentsp[callee()]({ a: 1.1 });
+f();

programming_language: JavaScript