JSC BytecodeGenerator::emitEqualityOpImpl Data Mishandling

Related Vulnerabilities: CVE-2019-8684  
Publish Date: 30 Jul 2019
                							

                JSC: A bug in BytecodeGenerator::emitEqualityOpImpl 

Related CVE Numbers: CVE-2019-8684.


PoC:
let a = (1 || typeof 1) === 'string';

Generated bytecode:
<global>#BPmgTo:[0x7ff1965a0000->0x7ff1965a8000, NoneGlobal, 37]: 11 instructions (0 wide instructions, 2 instructions with metadata); 225 bytes (188 metadata bytes); 1 parameter(s); 10 callee register(s); 6 variable(s); scope at loc4
[   0] enter              
[   1] get_scope          loc4
[   3] mov                loc5, loc4
[   6] check_traps        
[   7] mov                loc6, Undefined(const0)
[  10] resolve_scope      loc7, loc4, 0, GlobalProperty, 0
[  17] mov                loc8, Int32: 1(const1)
[  20] jtrue              loc8, 6(->26)
[  23] is_cell_with_type  loc8, Int32: 1(const1), StringType
[  27] put_to_scope       loc7, 0, loc8, 1048576<DoNotThrowIfNotFound|GlobalProperty|Initialization>, 0, 0
[  35] end                loc6

Identifiers:
  id0 = a

Constants:
   k0 = Undefined
   k1 = Int32: 1: in source as integer
   k2 = String (atomic) (identifier): string, StructureID: 9553

Here the jtrue instruction is pointing somewhere in the middle of the is_cell_with_type instruction. This is due to the bug in BytecodeGenerator::emitEqualityOpImpl which doesn't consider the case where m_lastOpcodeID is op_end which can indicate that the current position is a jump target. As a result, the method replaced wrongly the typeof instruction with the is_cell_with_type instruction.

Vulnerable method:
bool BytecodeGenerator::emitEqualityOpImpl(RegisterID* dst, RegisterID* src1, RegisterID* src2)
{
    if (m_lastInstruction->is<OpTypeof>()) {
        auto op = m_lastInstruction->as<OpTypeof>();
        if (src1->index() == op.m_dst.offset()
            && src1->isTemporary()
            && m_codeBlock->isConstantRegisterIndex(src2->index())
            && m_codeBlock->constantRegister(src2->index()).get().isString()) {
            const String& value = asString(m_codeBlock->constantRegister(src2->index()).get())->tryGetValue();
            if (value == \"undefined\") {
                rewind();
                OpIsUndefined::emit(this, dst, op.m_value);
                return true;
            }
            if (value == \"boolean\") {
                rewind();
                OpIsBoolean::emit(this, dst, op.m_value);
                return true;
            }
            if (value == \"number\") {
                rewind();
                OpIsNumber::emit(this, dst, op.m_value);
                return true;
            }
            if (value == \"string\") {
                rewind();
                OpIsCellWithType::emit(this, dst, op.m_value, StringType);
                return true;
            }
            if (value == \"symbol\") {
                rewind();
                OpIsCellWithType::emit(this, dst, op.m_value, SymbolType);
                return true;
            }
            if (Options::useBigInt() && value == \"bigint\") {
                rewind();
                OpIsCellWithType::emit(this, dst, op.m_value, BigIntType);
                return true;
            }
            if (value == \"object\") {
                rewind();
                OpIsObjectOrNull::emit(this, dst, op.m_value);
                return true;
            }
            if (value == \"function\") {
                rewind();
                OpIsFunction::emit(this, dst, op.m_value);
                return true;
            }
        }
    }

    return false;
}


This bug is subject to a 90 day disclosure deadline. After 90 days elapse
or a patch has been made broadly available (whichever is earlier), the bug
report will become visible to the public.



Found by: lokihardt@google.com

<p>