Monday, January 15, 2018

CVE-2017-7061: incorrect for-in optimization in JSC bytecode generator

适用版本: Safari 10.1.2 PoC: let o = {}; for (let i in {xx: 0}) { for (let j = 0; j < 2; j++) { o[i]; i = new Uint32Array([0, 1, 0x777777, 0, 0]); } }
+++ b/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp @@ -2843,15 +2843,15 @@ RegisterID* BytecodeGenerator::emitDeleteById(RegisterID* dst, RegisterID* base, RegisterID* BytecodeGenerator::emitGetByVal(RegisterID* dst, RegisterID* base, RegisterID* property) { - for (size_t i = m_forInContextStack.size(); i > 0; i--) { - ForInContext& context = m_forInContextStack[i - 1].get(); + for (size_t i = m_forInContextStack.size(); i--; ) { + ForInContext& context = m_forInContextStack[i].get(); if (context.local() != property) continue; - if (!context.isValid()) - break; + unsigned instIndex = instructions().size(); if (context.type() == ForInContext::IndexedForInContextType) { + static_cast<IndexedForInContext&>(context).addGetInst(instIndex, property->index()); property = static_cast<IndexedForInContext&>(context).index(); break; } @@ -2865,6 +2865,8 @@ RegisterID* BytecodeGenerator::emitGetByVal(RegisterID* dst, RegisterID* base, R instructions().append(structureContext.index()->index()); instructions().append(structureContext.enumerator()->index()); instructions().append(profile); + + structureContext.addGetInst(instIndex, property->index(), profile); return dst; } @@ -4555,6 +4557,9 @@ void BytecodeGenerator::popIndexedForInScope(RegisterID* localRegister) { if (!localRegister) return; + + ASSERT(m_forInContextStack.last()->type() == ForInContext::IndexedForInContextType); + static_cast<IndexedForInContext&>(m_forInContextStack.last().get()).finalize(*this); m_forInContextStack.removeLast(); } RegisterID* BytecodeGenerator::emitGetByVal(RegisterID* dst, RegisterID* base, RegisterID* property) { for (size_t i = m_forInContextStack.size(); i > 0; i--) { ForInContext& context = m_forInContextStack[i - 1].get(); if (context.local() != property) continue; if (!context.isValid()) break; if (context.type() == ForInContext::IndexedForInContextType) { property = static_cast<IndexedForInContext&>(context).index(); break; } ASSERT(context.type() == ForInContext::StructureForInContextType); StructureForInContext& structureContext = static_cast<StructureForInContext&>(context); UnlinkedValueProfile profile = emitProfiledOpcode(op_get_direct_pname); instructions().append(kill(dst)); instructions().append(base->index()); instructions().append(property->index()); instructions().append(structureContext.index()->index()); instructions().append(structureContext.enumerator()->index()); instructions().append(profile); return dst; } UnlinkedArrayProfile arrayProfile = newArrayProfile(); UnlinkedValueProfile profile = emitProfiledOpcode(op_get_by_val); instructions().append(kill(dst)); instructions().append(base->index()); instructions().append(property->index()); instructions().append(arrayProfile); instructions().append(profile); return dst; } 单独挑出来看其实不难, 挖洞最难的地方是在发现的思路, 而不是这个漏洞代码本身多么难懂. 非常好奇韩国神童到底是怎么想到这种思路的. 那么, 以上函数在以下 JS 代码执行的时候会被调用: func1{ let o = {}; for (let i in {xx: 0}) { o[i]; i = 0; o[i]; } } func2{ let o = {}; for (let i in {xx: 0}) { for (let j = 0; j < 2; j++) { o[i]; i = 0; } } } 在 func1, 第一次出现 o[i]的时候, 将产生 op_get_direct_pname 这个指令来处理数组下标, 在第二次出现 o[i]的时候, 因为没有通过类型检查, 这里不再适用之前的字节码. 而是使用 op_get_by_val 来处理. 这时候编译次数和执行次数是一样的, 并没有产生什么问题. 在 func2, 发生了一个很大的变化. 编译次数和执行次数不一样了. 之前所做的类型检查仅限于编译期, 而不是在 runtime, 所以这一句代码在 runtime 经历的改变是在编译期没有检查到的: 两次运行的字节码都是 op_get_direct_pname. 如果把字节码打出来的话会很清晰的看到这两个函数产生字节码的细小区别 - 就是这点差别导致了类型混淆.

No comments:

Post a Comment

Compiler Optimizations

Peephole optimization In  compiler theory , peephole optimization is a kind of  optimization  performed over a very small set of instruct...