[Webkit-unassigned] [Bug 281960] New: [WASM] JavaScriptCore Assertion Failed in JSC::Wasm::LLIntGenerator::checkConsistency()

bugzilla-daemon at webkit.org bugzilla-daemon at webkit.org
Wed Oct 23 02:25:01 PDT 2024


https://bugs.webkit.org/show_bug.cgi?id=281960

            Bug ID: 281960
           Summary: [WASM] JavaScriptCore Assertion Failed in
                    JSC::Wasm::LLIntGenerator::checkConsistency()
           Product: WebKit
           Version: WebKit Nightly Build
          Hardware: Unspecified
                OS: Unspecified
            Status: NEW
          Severity: Normal
          Priority: P2
         Component: WebAssembly
          Assignee: webkit-unassigned at lists.webkit.org
          Reporter: xiangwei1895 at gmail.com

JavaScriptCore Assertion Failed in JSC::Wasm::LLIntGenerator::checkConsistency()


I. Summary

The following sample causes jsc to have an assertion failed when compiling the wasm module. This problem exists in the latest jsc, compiled in debug mode (this assertion is ignored in release mode).

PoC:
-----------------------------------
load("wasm-module-builder.js");
const builder = new WasmModuleBuilder();
let $sig3 = builder.addType(kSig_i_iii);
let emptyFunc = builder.addFunction("emptyFunc", kSig_v_v)
                       .addBody([])
                       .exportFunc();
let main17 = builder.addFunction(undefined, $sig3).exportAs('main');
main17.addBody([
    ...wasmI32Const(1337),
    ...wasmI32Const(1337),
    kExprI32Eq,
    kExprRefFunc, emptyFunc.index,
    kExprBrOnNull, 0,
    kExprDrop, 
]);
const instance = builder.instantiate();
print(instance.exports.main(1, 2, 3));
-----------------------------------

Backtrace:
-----------------------------------
ASSERTION FAILED: expression == slot || expression.isConstant() || expression.isArgument() || static_cast<unsigned>(expression.toLocal()) < m_codeBlock->m_numVars
/data/workspace/WebKit/Source/JavaScriptCore/wasm/WasmLLIntGenerator.cpp(546) : auto JSC::Wasm::LLIntGenerator::checkConsistency()::(anonymous class)::operator()(JSC::VirtualRegister, JSC::VirtualRegister) const
(gdb) bt
#0  __pthread_kill_implementation (no_tid=0, signo=6, threadid=140735954261568)
    at ./nptl/pthread_kill.c:44
#1  __pthread_kill_internal (signo=6, threadid=140735954261568) at ./nptl/pthread_kill.c:78
#2  __GI___pthread_kill (threadid=140735954261568, signo=signo at entry=6) at ./nptl/pthread_kill.c:89
#3  0x00007ffff5949476 in __GI_raise (sig=sig at entry=6) at ../sysdeps/posix/raise.c:26
#4  0x00007ffff592f7f3 in __GI_abort () at ./stdlib/abort.c:79
#5  0x000055555560804b in WTFCrashWithInfo ()
    at /data/workspace/WebKit/wasmdebug/JSCOnly/Debug/WTF/Headers/wtf/Assertions.h:912
#6  0x00005555571433ea in JSC::Wasm::LLIntGenerator::checkConsistency()::{lambda(JSC::VirtualRegister, JSC::VirtualRegister)#2}::operator()(JSC::VirtualRegister, JSC::VirtualRegister) const (this=0x7fffa48e61a8, 
    expression=..., slot=...)
    at /data/workspace/WebKit/Source/JavaScriptCore/wasm/WasmLLIntGenerator.cpp:546
#7  0x00005555571432d6 in JSC::Wasm::LLIntGenerator::walkExpressionStack<WTF::Vector<JSC::Wasm::FunctionParserTypes<JSC::Wasm::LLIntGenerator::ControlType, JSC::VirtualRegister, JSC::CallLinkInfoBase::CallType>::TypedExpression, 16ul, WTF::UnsafeVectorOverflow, 16ul, WTF::FastMalloc>, JSC::Wasm::LLIntGenerator::checkConsistency()::{lambda(JSC::VirtualRegister, JSC::VirtualRegister)#2}>(WTF::Vector<JSC::Wasm::FunctionParserTypes<JSC::Wasm::LLIntGenerator::ControlType, JSC::VirtualRegister, JSC::CallLinkInfoBase::CallType>::TypedExpression, 16ul, WTF::UnsafeVectorOverflow, 16ul, WTF::FastMalloc>&, unsigned int, JSC::Wasm::LLIntGenerator::checkConsistency()::{lambda(JSC::VirtualRegister, JSC::VirtualRegister)#2} const&) (
    this=0x7fffa48f8720, expressionStack=..., stackSize=20, functor=...) at /data/workspace/WebKit/Source/JavaScriptCore/wasm/WasmLLIntGenerator.cpp:513
#8  0x00005555571430f3 in JSC::Wasm::LLIntGenerator::walkExpressionStack<WTF::Vector<JSC::Wasm::FunctionParserTypes<JSC::Wasm::LLIntGenerator::ControlType, JSC::VirtualRegister, JSC::CallLinkInfoBase::CallType>::TypedExpression, 16ul, WTF::UnsafeVectorOverflow, 16ul, WTF::FastMalloc>, JSC::Wasm::LLIntGenerator::checkConsistency()::{lambda(JSC::VirtualRegister, JSC::VirtualRegister)#2}>(WTF::Vector<JSC::Wasm::FunctionParserTypes<JSC::Wasm::LLIntGenerator::ControlType, JSC::VirtualRegister, JSC::CallLinkInfoBase::CallType>::TypedExpression, 16ul, WTF::UnsafeVectorOverflow, 16ul, WTF::FastMalloc>&, JSC::Wasm::LLIntGenerator::checkConsistency()::{lambda(JSC::VirtualRegister, JSC::VirtualRegister)#2} const&) (this=0x7fffa48f8720, expressionStack=..., functor=...) at /data/workspace/WebKit/Source/JavaScriptCore/wasm/WasmLLIntGenerator.cpp:520
#9  0x000055555712ba26 in JSC::Wasm::LLIntGenerator::checkConsistency (this=0x7fffa48f8720) at /data/workspace/WebKit/Source/JavaScriptCore/wasm/WasmLLIntGenerator.cpp:545
#10 0x0000555557124639 in JSC::Wasm::LLIntGenerator::push (this=0x7fffa48f8720) at /data/workspace/WebKit/Source/JavaScriptCore/wasm/WasmLLIntGenerator.cpp:283
#11 0x000055555713055b in JSC::Wasm::LLIntGenerator::addBranchNull (this=0x7fffa48f8720, data=..., reference=..., returnValues=..., shouldNegate=false, result=...) at /data/workspace/WebKit/Source/JavaScriptCore/wasm/WasmLLIntGenerator.cpp:1432
#12 0x00005555571de829 in JSC::Wasm::FunctionParser<JSC::Wasm::LLIntGenerator>::parseExpression (this=0x7fffa48f4d70) at /data/workspace/WebKit/Source/JavaScriptCore/wasm/WasmFunctionParser.h:2918
#13 0x00005555571b8b73 in JSC::Wasm::FunctionParser<JSC::Wasm::LLIntGenerator>::parseBody (this=0x7fffa48f4d70) at /data/workspace/WebKit/Source/JavaScriptCore/wasm/WasmFunctionParser.h:525
#14 0x000055555712382f in JSC::Wasm::FunctionParser<JSC::Wasm::LLIntGenerator>::parse (this=0x7fffa48f4d70) at /data/workspace/WebKit/Source/JavaScriptCore/wasm/WasmFunctionParser.h:478
#15 0x0000555557122a39 in JSC::Wasm::parseAndCompileBytecode (function=std::span of length 14 = {...}, signature=..., info=..., functionIndex=...) at /data/workspace/WebKit/Source/JavaScriptCore/wasm/WasmLLIntGenerator.cpp:634
#16 0x000055555713a892 in JSC::Wasm::LLIntPlan::compileFunction (this=0x7fffec048210, functionIndex=...) at /data/workspace/WebKit/Source/JavaScriptCore/wasm/WasmLLIntPlan.cpp:103
#17 0x000055555708566d in JSC::Wasm::EntryPlan::compileFunctions (this=0x7fffec048210, effort=JSC::Wasm::Plan::Partial) at /data/workspace/WebKit/Source/JavaScriptCore/wasm/WasmEntryPlan.cpp:218
#18 0x000055555713c309 in JSC::Wasm::LLIntPlan::work (this=0x7fffec048210, effort=JSC::Wasm::Plan::Partial) at /data/workspace/WebKit/Source/JavaScriptCore/wasm/WasmLLIntPlan.cpp:247
#19 0x000055555743102a in JSC::Wasm::Worklist::Thread::work (this=0x7fffec0305e0) at /data/workspace/WebKit/Source/JavaScriptCore/wasm/WasmWorklist.cpp:108
#20 0x0000555557c10f0b in WTF::AutomaticThread::start(WTF::AbstractLocker const&)::$_0::operator()() const (this=0x7fffec145d68) at /data/workspace/WebKit/Source/WTF/wtf/AutomaticThread.cpp:225
#21 0x0000555557c10ba9 in WTF::Detail::CallableWrapper<WTF::AutomaticThread::start(WTF::AbstractLocker const&)::$_0, void>::call() (this=0x7fffec145d60) at /data/workspace/WebKit/Source/WTF/wtf/Function.h:53
#22 0x0000555555f63a97 in WTF::Function<void ()>::operator()() const (this=0x7fffa48f8e20) at /data/workspace/WebKit/Source/WTF/wtf/Function.h:82
#23 0x0000555557def879 in WTF::Thread::entryPoint (newThreadContext=0x7fffec1128e0) at /data/workspace/WebKit/Source/WTF/wtf/Threading.cpp:266
#24 0x0000555557e643d5 in WTF::wtfThreadEntryPoint (context=0x7fffec1128e0) at /data/workspace/WebKit/Source/WTF/wtf/posix/ThreadingPOSIX.cpp:239
#25 0x00007ffff599bac3 in start_thread (arg=<optimized out>) at ./nptl/pthread_create.c:442
#26 0x00007ffff5a2d850 in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81
-----------------------------------

II. Root cause analysis

The inconsistency between `expression` and `slot` is mainly due to the asynchrony between `m_stackSize` and `expression_stack`.

Reason for asynchrony: When `shouldNegate` is false, the `addBranchNull`, two `push()` are performed consecutively in `addBranchNull`. The `push()` causes m_stackSize to increase, but `expression_stack` is not updated. This causes `checkConsistency()` to report assertion failed during the second push.

```
void checkConsistency()
{
#if ASSERT_ENABLED
    // The rules for locals and constants in the stack are:
    // 1) Locals have to be materialized whenever a control entry is pushed to the control stack (i.e. every time we splitStack)
    //    NOTE: This is a trade-off so that set_local does not have to walk up the control stack looking for delayed get_locals
    // 2) If the control entry is a loop, we also need to materialize constants in the newStack, since those slots will be written
    //    to from loop back edges
    // 3) Both locals and constants have to be materialized before branches, since multiple branches might share the same target,
    //    we can't make any assumptions about the stack state at that point, so we materialize the stack.
    for (ControlEntry& controlEntry : m_parser->controlStack()) {
        walkExpressionStack(controlEntry, [&](VirtualRegister expression, VirtualRegister slot) {
            ASSERT(expression == slot || expression.isConstant());
        });
    }
    walkExpressionStack(m_parser->expressionStack(), [&](VirtualRegister expression, VirtualRegister slot) {
        ASSERT(expression == slot || expression.isConstant() || expression.isArgument() || static_cast<unsigned>(expression.toLocal()) < m_codeBlock->m_numVars);
    });
#endif // ASSERT_ENABLED
}

auto LLIntGenerator::addBranchNull(ControlType& data, ExpressionType reference, Stack& returnValues, bool shouldNegate, ExpressionType& result) -> PartialResult
{
    // Leave a hole for the reference and avoid overwriting it with the condition.
    if (!shouldNegate)
        push();

    auto condition = push();
}

enum NoConsistencyCheckTag { NoConsistencyCheck };
ExpressionType push(NoConsistencyCheckTag)
{
    m_maxStackSize = std::max(m_maxStackSize, ++m_stackSize);
    return virtualRegisterForLocal(m_stackSize - 1);
}

ExpressionType push()
{
    checkConsistency();
    return push(NoConsistencyCheck);
}
```

This bug creates a mismatch between the expression stack and slot allocations in the WebAssembly compiler, triggering assertion failures during consistency checks. Although currently not directly exploitable, this discrepancy compromises the compiler's internal state coherence and poses potential security risks. It potentially opens avenues for memory corruption, type confusion, and other vulnerabilities. The flaw could lead to incorrect code generation and runtime errors, and might serve as a stepping stone for more severe exploits when combined with other issues or future changes. 

If possible, I would like you to assign a cve id to this issue.

III. Reproduce 

JavaScript version:     b4239a38c109a1f5980fc9c1cfe9a98cd3328741
Build platform:     Ubuntu 22.04.2 LTS
Build Command: ./Tools/Scripts/build-jsc --debug --jsc-only
Execution Command: ./jsc poc.js

-- 
You are receiving this mail because:
You are the assignee for the bug.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.webkit.org/pipermail/webkit-unassigned/attachments/20241023/5e3ea36a/attachment.htm>


More information about the webkit-unassigned mailing list