CVE-2023-30470 (JavaScript, published 2023-05-18T22:15Z)
Description: value: 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.
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:
lib/Optimizer/Scalar/SimpleCallGraphProvider.cpp
test/Optimizer/non-strict-opts.js
test/hermes/regress-arguments-callee.js
Commit message: 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


Vulnerable code:
// CHECK:function foo#1#2(p1 : number)#3 : string

Fixed code:
  // 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;
  }

// CHECK:function foo#1#2(p1)#3 : string
/**
 * 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();