Skip to content

Heap-buffer-overflow in ExecutionPauser.cpp #1413

@seongheunh

Description

@seongheunh

Escargot (please complete the following information):

  • OS: Ubuntu 22.04
  • Revision : available version(commit) : 5f9aefa
  • build setting (address sanitizer enabled)
cmake -DESCARGOT_MODE=debug -DESCARGOT_OUTPUT=shell -GNinja -DCMAKE_C_FLAGS="-fsanitize=address" -DCMAKE_CXX_FLAGS="-fsanitize=address"

Describe the bug
Heap-buffer overflow by resizing heap (bytecodeblockdata realloc)

Test case
Test code to reproduce the behavior:

function main() {
async function* f0(a1) {
    let v0 = yield;
    eval(a1);
    return v0;
}
let v1 = f0(1);
v1.next();
v1.next();
}; main();

Backtrace

=================================================================
==3607==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x61a000000558 at pc 0x5d10fb2b6c4d bp 0x7ffd22a087c0 sp 0x7ffd22a087b0
READ of size 8 at 0x61a000000558 thread T0
    #0 0x5d10fb2b6c4c in Escargot::Interpreter::interpret(Escargot::ExecutionState*, Escargot::ByteCodeBlock*, unsigned long, Escargot::Value*) /home/ubuntu22/escargot_asan/src/interpreter/ByteCodeInterpreter.cpp:247
    #1 0x5d10fb74345a in Escargot::ExecutionPauser::start(Escargot::ExecutionState&, Escargot::ExecutionPauser*, Escargot::Object*, Escargot::Value const&, bool, bool, Escargot::ExecutionPauser::StartFrom) /home/ubuntu22/escargot_asan/src/runtime/ExecutionPauser.cpp:186
    #2 0x5d10fb884f3a in awaitFulfilledFunctions /home/ubuntu22/escargot_asan/src/runtime/ScriptAsyncFunctionObject.cpp:109
    #3 0x5d10fb885024 in awaitFulfilledFunction /home/ubuntu22/escargot_asan/src/runtime/ScriptAsyncFunctionObject.cpp:117
    #4 0x5d10fb7d9432 in Escargot::Value Escargot::NativeFunctionObject::processNativeFunctionCall<false, true>(Escargot::ExecutionState&, Escargot::Value const&, unsigned long, Escargot::Value*, Escargot::Optional<Escargot::Object*>) /home/ubuntu22/escargot_asan/src/runtime/FunctionObjectInlines.h:312
    #5 0x5d10fb7d88df in Escargot::NativeFunctionObject::call(Escargot::ExecutionState&, Escargot::Value const&, unsigned long, Escargot::Value*) /home/ubuntu22/escargot_asan/src/runtime/NativeFunctionObject.cpp:78
    #6 0x5d10fb7f4783 in Escargot::Object::call(Escargot::ExecutionState&, Escargot::Value const&, Escargot::Value const&, unsigned long, Escargot::Value*) /home/ubuntu22/escargot_asan/src/runtime/Object.cpp:1332
    #7 0x5d10fb7c58f8 in operator() /home/ubuntu22/escargot_asan/src/runtime/Job.cpp:71
    #8 0x5d10fb7c5f25 in _FUN /home/ubuntu22/escargot_asan/src/runtime/Job.cpp:87
    #9 0x5d10fb87b3a8 in Escargot::SandBox::run(Escargot::ExecutionState&, Escargot::Value (*)(Escargot::ExecutionState&, void*), void*) /home/ubuntu22/escargot_asan/src/runtime/SandBox.cpp:123
    #10 0x5d10fb7c627b in Escargot::PromiseReactionJob::run() /home/ubuntu22/escargot_asan/src/runtime/Job.cpp:88
    #11 0x5d10fb90f385 in Escargot::VMInstance::executePendingJob() /home/ubuntu22/escargot_asan/src/runtime/VMInstance.cpp:811
    #12 0x5d10fb062252 in Escargot::VMInstanceRef::executePendingJob() /home/ubuntu22/escargot_asan/src/api/EscargotPublic.cpp:1231
    #13 0x5d10fb93b066 in evalScript /home/ubuntu22/escargot_asan/src/shell/Shell.cpp:812
    #14 0x5d10fb93d5d1 in main /home/ubuntu22/escargot_asan/src/shell/Shell.cpp:1149
    #15 0x7aee15229d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #16 0x7aee15229e3f in __libc_start_main_impl ../csu/libc-start.c:392
    #17 0x5d10fb0428a4 in _start (/home/ubuntu22/escargot_asan/escargot+0x2ab8a4)

0x61a000000558 is located 0 bytes to the right of 1240-byte region [0x61a000000080,0x61a000000558)
allocated by thread T0 here:
    #0 0x7aee15ab4c38 in __interceptor_realloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:164
    #1 0x5d10fb2806a7 in Escargot::ByteCodeBlockData::shrinkToFit() /home/ubuntu22/escargot_asan/src/interpreter/ByteCodeBlockData.h:132
    #2 0x5d10fb2830d2 in Escargot::ByteCodeGenerator::generateByteCode(Escargot::Context*, Escargot::InterpretedCodeBlock*, Escargot::Node*, bool, bool) (/home/ubuntu22/escargot_asan/escargot+0x4ec0d2)
    #3 0x5d10fb4b6953 in Escargot::ScriptParser::generateFunctionByteCode(Escargot::ExecutionState&, Escargot::InterpretedCodeBlock*) /home/ubuntu22/escargot_asan/src/parser/ScriptParser.cpp:533
    #4 0x5d10fb891034 in Escargot::ScriptFunctionObject::generateByteCodeBlock(Escargot::ExecutionState&) /home/ubuntu22/escargot_asan/src/runtime/ScriptFunctionObject.cpp:68
    #5 0x5d10fb888861 in Escargot::Value Escargot::FunctionObjectProcessCallGenerator::processCall<Escargot::ScriptAsyncGeneratorFunctionObject, false, false, false, Escargot::ScriptAsyncGeneratorFunctionObjectThisValueBinder, Escargot::FunctionObjectNewTargetBinder, Escargot::FunctionObjectReturnValueBinder>(Escargot::ExecutionState&, Escargot::ScriptAsyncGeneratorFunctionObject*, Escargot::Value const&, unsigned long, Escargot::Value*, Escargot::Object*) /home/ubuntu22/escargot_asan/src/runtime/FunctionObjectInlines.h:101
    #6 0x5d10fb8883de in Escargot::ScriptAsyncGeneratorFunctionObject::call(Escargot::ExecutionState&, Escargot::Value const&, unsigned long, Escargot::Value*) /home/ubuntu22/escargot_asan/src/runtime/ScriptAsyncGeneratorFunctionObject.cpp:73
    #7 0x5d10fb2bc11a in Escargot::Interpreter::interpret(Escargot::ExecutionState*, Escargot::ByteCodeBlock*, unsigned long, Escargot::Value*) /home/ubuntu22/escargot_asan/src/interpreter/ByteCodeInterpreter.cpp:770
    #8 0x5d10fb2dfd66 in Escargot::InterpreterSlowPath::blockOperation(Escargot::ExecutionState*&, Escargot::BlockOperation*, unsigned long&, Escargot::ByteCodeBlock*, Escargot::Value*) /home/ubuntu22/escargot_asan/src/interpreter/ByteCodeInterpreter.cpp:3895
    #9 0x5d10fb2c0c0c in Escargot::Interpreter::interpret(Escargot::ExecutionState*, Escargot::ByteCodeBlock*, unsigned long, Escargot::Value*) /home/ubuntu22/escargot_asan/src/interpreter/ByteCodeInterpreter.cpp:1500
    #10 0x5d10fb8935f6 in Escargot::Value Escargot::FunctionObjectProcessCallGenerator::processCall<Escargot::ScriptFunctionObject, false, false, false, Escargot::FunctionObjectThisValueBinder, Escargot::FunctionObjectNewTargetBinder, Escargot::FunctionObjectReturnValueBinder>(Escargot::ExecutionState&, Escargot::ScriptFunctionObject*, Escargot::Value const&, unsigned long, Escargot::Value*, Escargot::Object*) /home/ubuntu22/escargot_asan/src/runtime/FunctionObjectInlines.h:221
    #11 0x5d10fb891750 in Escargot::ScriptFunctionObject::call(Escargot::ExecutionState&, Escargot::Value const&, unsigned long, Escargot::Value*) /home/ubuntu22/escargot_asan/src/runtime/ScriptFunctionObject.cpp:108
    #12 0x5d10fb2bc11a in Escargot::Interpreter::interpret(Escargot::ExecutionState*, Escargot::ByteCodeBlock*, unsigned long, Escargot::Value*) /home/ubuntu22/escargot_asan/src/interpreter/ByteCodeInterpreter.cpp:770
    #13 0x5d10fb49b234 in Escargot::Script::execute(Escargot::ExecutionState&, bool, bool) /home/ubuntu22/escargot_asan/src/parser/Script.cpp:499
    #14 0x5d10fb06587e in Escargot::ScriptRef::execute(Escargot::ExecutionStateRef*) /home/ubuntu22/escargot_asan/src/api/EscargotPublic.cpp:4736
    #15 0x5d10fb93a2d0 in operator() /home/ubuntu22/escargot_asan/src/shell/Shell.cpp:790
    #16 0x5d10fb93a2fb in _FUN /home/ubuntu22/escargot_asan/src/shell/Shell.cpp:791
    #17 0x5d10fb9440b7 in decltype (((forward<Escargot::ValueRef* (*&)(Escargot::ExecutionStateRef*, Escargot::ScriptRef*)>)({parm#1}))((forward<Escargot::ExecutionStateRef*&>)({parm#3}), (forward<Escargot::ScriptRef*&>)({parm#3}))) Escargot::EvaluatorUtil::ApplyTupleIntoArgumentsOfVariadicTemplateFunction<0ul>::apply<Escargot::ValueRef* (*&)(Escargot::ExecutionStateRef*, Escargot::ScriptRef*), std::tuple<Escargot::ExecutionStateRef*, Escargot::ScriptRef*>&, Escargot::ExecutionStateRef*&, Escargot::ScriptRef*&>(Escargot::ValueRef* (*&)(Escargot::ExecutionStateRef*, Escargot::ScriptRef*), std::tuple<Escargot::ExecutionStateRef*, Escargot::ScriptRef*>&, Escargot::ExecutionStateRef*&, Escargot::ScriptRef*&) /home/ubuntu22/escargot_asan/src/api/EscargotPublic.h:521
    #18 0x5d10fb9436d6 in decltype (Escargot::EvaluatorUtil::ApplyTupleIntoArgumentsOfVariadicTemplateFunction<0ul>::apply((forward<Escargot::ValueRef* (*&)(Escargot::ExecutionStateRef*, Escargot::ScriptRef*)>)({parm#1}), (forward<std::tuple<Escargot::ExecutionStateRef*, Escargot::ScriptRef*>&>)({parm#2}), (get<(1ul)-(1)>)((forward<std::tuple<Escargot::ExecutionStateRef*, Escargot::ScriptRef*>&>)({parm#2})), (forward<Escargot::ScriptRef*&>)({parm#3}))) Escargot::EvaluatorUtil::ApplyTupleIntoArgumentsOfVariadicTemplateFunction<1ul>::apply<Escargot::ValueRef* (*&)(Escargot::ExecutionStateRef*, Escargot::ScriptRef*), std::tuple<Escargot::ExecutionStateRef*, Escargot::ScriptRef*>&, Escargot::ScriptRef*&>(Escargot::ValueRef* (*&)(Escargot::ExecutionStateRef*, Escargot::ScriptRef*), std::tuple<Escargot::ExecutionStateRef*, Escargot::ScriptRef*>&, Escargot::ScriptRef*&) /home/ubuntu22/escargot_asan/src/api/EscargotPublic.h:510
    #19 0x5d10fb942c96 in decltype (Escargot::EvaluatorUtil::ApplyTupleIntoArgumentsOfVariadicTemplateFunction<1ul>::apply((forward<Escargot::ValueRef* (*&)(Escargot::ExecutionStateRef*, Escargot::ScriptRef*)>)({parm#1}), (forward<std::tuple<Escargot::ExecutionStateRef*, Escargot::ScriptRef*>&>)({parm#2}), (get<(2ul)-(1)>)((forward<std::tuple<Escargot::ExecutionStateRef*, Escargot::ScriptRef*>&>)({parm#2})))) Escargot::EvaluatorUtil::ApplyTupleIntoArgumentsOfVariadicTemplateFunction<2ul>::apply<Escargot::ValueRef* (*&)(Escargot::ExecutionStateRef*, Escargot::ScriptRef*), std::tuple<Escargot::ExecutionStateRef*, Escargot::ScriptRef*>&>(Escargot::ValueRef* (*&)(Escargot::ExecutionStateRef*, Escargot::ScriptRef*), std::tuple<Escargot::ExecutionStateRef*, Escargot::ScriptRef*>&) /home/ubuntu22/escargot_asan/src/api/EscargotPublic.h:510
    #20 0x5d10fb941e50 in decltype (Escargot::EvaluatorUtil::ApplyTupleIntoArgumentsOfVariadicTemplateFunction<std::tuple_size<std::decay<std::tuple<Escargot::ExecutionStateRef*, Escargot::ScriptRef*>&>::type>::value>::apply((forward<Escargot::ValueRef* (*&)(Escargot::ExecutionStateRef*, Escargot::ScriptRef*)>)({parm#1}), (forward<std::tuple<Escargot::ExecutionStateRef*, Escargot::ScriptRef*>&>)({parm#2}))) Escargot::EvaluatorUtil::applyTupleIntoArgumentsOfVariadicTemplateFunction<Escargot::ValueRef* (*&)(Escargot::ExecutionStateRef*, Escargot::ScriptRef*), std::tuple<Escargot::ExecutionStateRef*, Escargot::ScriptRef*>&>(Escargot::ValueRef* (*&)(Escargot::ExecutionStateRef*, Escargot::ScriptRef*), std::tuple<Escargot::ExecutionStateRef*, Escargot::ScriptRef*>&) /home/ubuntu22/escargot_asan/src/api/EscargotPublic.h:531
    #21 0x5d10fb9404c0 in Escargot::Evaluator::executeImpl<Escargot::ContextRef, Escargot::ScriptRef*>(Escargot::ContextRef*, Escargot::ValueRef* (*)(Escargot::ExecutionStateRef*, Escargot::ScriptRef*), Escargot::ScriptRef*)::{lambda(Escargot::ExecutionStateRef*, void*, void*)#1}::operator()(Escargot::ExecutionStateRef*, void*, void*) const /home/ubuntu22/escargot_asan/src/api/EscargotPublic.h:612
    #22 0x5d10fb94054e in Escargot::Evaluator::executeImpl<Escargot::ContextRef, Escargot::ScriptRef*>(Escargot::ContextRef*, Escargot::ValueRef* (*)(Escargot::ExecutionStateRef*, Escargot::ScriptRef*), Escargot::ScriptRef*)::{lambda(Escargot::ExecutionStateRef*, void*, void*)#1}::_FUN(Escargot::ExecutionStateRef*, void*, void*) /home/ubuntu22/escargot_asan/src/api/EscargotPublic.h:606
    #23 0x5d10fb0619f0 in operator() /home/ubuntu22/escargot_asan/src/api/EscargotPublic.cpp:1094
    #24 0x5d10fb061a2a in _FUN /home/ubuntu22/escargot_asan/src/api/EscargotPublic.cpp:1095
    #25 0x5d10fb87b124 in Escargot::SandBox::run(Escargot::Value (*)(Escargot::ExecutionState&, void*), void*) /home/ubuntu22/escargot_asan/src/runtime/SandBox.cpp:111
    #26 0x5d10fb061c88 in Escargot::Evaluator::executeFunction(Escargot::ContextRef*, Escargot::ValueRef* (*)(Escargot::ExecutionStateRef*, void*, void*), void*, void*) /home/ubuntu22/escargot_asan/src/api/EscargotPublic.cpp:1096
    #27 0x5d10fb94074a in Escargot::Evaluator::EvaluatorResult Escargot::Evaluator::executeImpl<Escargot::ContextRef, Escargot::ScriptRef*>(Escargot::ContextRef*, Escargot::ValueRef* (*)(Escargot::ExecutionStateRef*, Escargot::ScriptRef*), Escargot::ScriptRef*) /home/ubuntu22/escargot_asan/src/api/EscargotPublic.h:614
    #28 0x5d10fb93ecbf in execute<Escargot::ScriptRef*, evalScript(Escargot::ContextRef*, Escargot::StringRef*, Escargot::StringRef*, bool, bool)::<lambda(Escargot::ExecutionStateRef*, Escargot::ScriptRef*)> > /home/ubuntu22/escargot_asan/src/api/EscargotPublic.h:585
    #29 0x5d10fb93aae5 in evalScript /home/ubuntu22/escargot_asan/src/shell/Shell.cpp:792

SUMMARY: AddressSanitizer: heap-buffer-overflow /home/ubuntu22/escargot_asan/src/interpreter/ByteCodeInterpreter.cpp:247 in Escargot::Interpreter::interpret(Escargot::ExecutionState*, Escargot::ByteCodeBlock*, unsigned long, Escargot::Value*)
Shadow bytes around the buggy address:
  0x0c347fff8050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c347fff8060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c347fff8070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c347fff8080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c347fff8090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c347fff80a0: 00 00 00 00 00 00 00 00 00 00 00[fa]fa fa fa fa
  0x0c347fff80b0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c347fff80c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c347fff80d0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c347fff80e0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c347fff80f0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
  Shadow gap:              cc
==3607==ABORTING

** Root cause Analysis **

  1. first, we lets see last function of crash log
// ByteCodeInterpreter.cpp
Value Interpreter::interpret(ExecutionState* state, ByteCodeBlock* byteCodeBlock, size_t programCounter, Value* registerFile)
{
				...
        goto*(((ByteCode*)programCounter)->m_opcodeInAddress); #line 247
  • heap-buffer-overflow can occur While Interpreter::interpret performing, when we execute goto*(((ByteCode*)programCounter)->m_opcodeInAddress);

  • This programCounter is an argument passed from the calling function, and the calling function is as follows.

Value ExecutionPauser::start(ExecutionState& state, ExecutionPauser* self, Object* source, const Value& resumeValue, bool isAbruptReturn, bool isAbruptThrow, StartFrom from)
{
   ...
    // AsyncFunction
    // https://www.ecma-international.org/ecma-262/10.0/index.html#sec-async-functions-abstract-operations-async-function-start
    // AsyncGeneratorFunction
    // https://www.ecma-international.org/ecma-262/10.0/index.html#sec-asyncgeneratorstart

    const size_t programStart = reinterpret_cast<size_t>(self->m_byteCodeBlock->m_code.data());
    Value result;
    try {
        ExecutionState* es;
        size_t startPos = self->m_byteCodePosition;
        if (startPos == SIZE_MAX) {
            // need to fresh start
            startPos = programStart;
            es = self->m_executionState;
        } else {
            // resume
            startPos = reinterpret_cast<size_t>(self->m_pausedCode.data());

            LexicalEnvironment* env;
            if (originalState->resolveCallee() && originalState->resolveCallee()->isScriptFunctionObject()) {
                env = new LexicalEnvironment(new FunctionEnvironmentRecordOnHeap<false, false>(originalState->resolveCallee()->asScriptFunctionObject()), nullptr
    ...

        result = Interpreter::interpret(es, self->m_byteCodeBlock, startPos, self->m_registerFile);
  • In ExcutionPauser, startPos is called with self→m_byteCodePosition to perform Interpreter::interpret.

  • This ExecutionPauser is used asynchronously from awaitFullfilledFuction.

static Value awaitFulfilledFunction(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget)
{
    ScriptAsyncFunctionHelperFunctionObject* self = (ScriptAsyncFunctionHelperFunctionObject*)state.resolveCallee();
    awaitFulfilledFunctions(state, self, argv[0]);
    return Value();
}

static void awaitFulfilledFunctions(ExecutionState& state, ScriptAsyncFunctionHelperFunctionObject* F, const Value& value)
{
    // Let F be the active function object.
    // Let asyncContext be F.[[AsyncContext]].
    // Let prevContext be the running execution context.
    // Suspend prevContext.
    // Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
    // Resume the suspended evaluation of asyncContext using NormalCompletion(value) as the result of the operation that suspended it.
    ExecutionPauser::start(state, F->m_executionPauser, F->m_source, value, false, false, F->m_source->isAsyncGeneratorObject() ? ExecutionPauser::StartFrom::AsyncGenerator : ExecutionPauser::StartFrom::Async);
    // Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context.
    // Return undefined.
}
  • As a result, the function is processed asynchronously at the point where the function progress is stopped and executed repeatedly, and the operation is performed by utilizing the address held by the ExecutionPauser object.

  • Meanwhile, another thread shrinks the address space for bytecode optimization.

void ScriptParser::generateFunctionByteCode(ExecutionState& state, InterpretedCodeBlock* codeBlock)
{
    ...
        codeBlock->m_byteCodeBlock = ByteCodeGenerator::generateByteCode(state.context(), codeBlock, functionNode);
 
        
ByteCodeBlock* ByteCodeGenerator::generateByteCode(Context* context, InterpretedCodeBlock* codeBlock, Node* ast, bool inWithFromRuntime, bool cacheByteCode)
{
    ...
    ByteCodeBlock* block = new ByteCodeBlock(codeBlock);
    ...
    ByteCodeGenerateContext ctx(codeBlock, block, codeBlock->isGlobalScope(), codeBlock->isEvalCode(), inWithFromRuntime || codeBlock->inWith(), nData);
    ...
    // generate bytecode
    ast->generateStatementByteCode(block, &ctx);
    ...
    block->m_code.shrinkToFit();
  • This shrinkToFit function performs reallocation to fit the size of the bytecode to be created, and in the process, the occupied size is reduced.
void shrinkToFit()
    {
        ASSERT(m_size <= m_capacity);
        if (m_size != m_capacity) {
            if (m_size) {
                ASSERT(!!m_buffer);
                m_buffer = reinterpret_cast<uint8_t*>(realloc(m_buffer, m_size));
                m_capacity = m_size;
            } else {
                clear();
            }
        }
    }
  • When the engine runs a code that causes a pause and resume operation using yield and next() in the PoC, the resume location is remembered by ExecutionPauser during the operation and the operation is performed, and on the other hand, the address is reallocated during the bytecode generation process.
  • After reallocation via ByteCodeBlockData::shrinkToFit(), the location to resume in ExecutionPauser is not synchronized with the shrinkToFit state, resulting in a heap-buffer-overflow.

Expected behavior
this bug is associated with pc(program counter), so I expect that malicious actors will be able to control the flow.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions