Share
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