From 760822c7bf4ffd5e773da14bc35d9c07d672f0c7 Mon Sep 17 00:00:00 2001 From: Rajat Dua Date: Tue, 28 Nov 2017 18:50:04 -0800 Subject: [PATCH] [CVE-2017-11893] JIT Op_MaxInAnArray and Op_MinInAnArray can explicitly call user defined JavaScript functions - Google, Inc. --- lib/Backend/Inline.cpp | 91 ++++++++++++++++++++++++++---------------- lib/Backend/Inline.h | 6 +-- 2 files changed, 59 insertions(+), 38 deletions(-) diff --git a/lib/Backend/Inline.cpp b/lib/Backend/Inline.cpp index c52f3eff0ba..b5b6431ec19 100644 --- a/lib/Backend/Inline.cpp +++ b/lib/Backend/Inline.cpp @@ -1983,19 +1983,7 @@ Inline::InlineBuiltInFunction(IR::Instr *callInstr, const FunctionJITTimeInfo * StackSym* originalCallTargetStackSym = callInstr->GetSrc1()->GetStackSym(); bool originalCallTargetOpndIsJITOpt = callInstr->GetSrc1()->GetIsJITOptimizedReg(); - // We are committed to inlining, optimize the call instruction for fixed fields now and don't attempt it later. - bool safeThis = false; - if (TryOptimizeCallInstrWithFixedMethod(callInstr, inlineeData, false /*isPolymorphic*/, true /*isBuiltIn*/, false /*isCtor*/, true /*isInlined*/, safeThis /*unused here*/)) - { - Assert(callInstr->m_opcode == Js::OpCode::CallIFixed); - Assert(callInstr->GetFixedFunction()->GetFuncInfoAddr() == inlineeData->GetFunctionInfoAddr()); - } - else - { - // FunctionObject check for built-ins - IR::BailOutInstr * bailOutInstr = IR::BailOutInstr::New(Js::OpCode::BailOnNotBuiltIn, IR::BailOutOnInlineFunction, callInstr, callInstr->m_func); - InsertFunctionObjectCheck(callInstr, callInstr, bailOutInstr, inlineeData); - } + IR::ByteCodeUsesInstr* useCallTargetInstr = EmitFixedMethodOrFunctionObjectChecksForBuiltIns(callInstr, callInstr, inlineeData, false, true, false, true); // To push function object for cases when we have to make calls to helper method to assist in inlining if(inlineCallOpCode == Js::OpCode::CallDirect) @@ -2031,11 +2019,9 @@ Inline::InlineBuiltInFunction(IR::Instr *callInstr, const FunctionJITTimeInfo * } } - // Insert a byteCodeUsesInstr to make sure the function object's lifetime is extended beyond the last bailout point - // at which we may need to call the inlinee again in the interpreter. + if (useCallTargetInstr) { - IR::ByteCodeUsesInstr * useCallTargetInstr = IR::ByteCodeUsesInstr::New(callInstr); - useCallTargetInstr->SetRemovedOpndSymbol(originalCallTargetOpndIsJITOpt, originalCallTargetStackSym->m_id); + useCallTargetInstr->Unlink(); callInstr->InsertBefore(useCallTargetInstr); } @@ -2071,7 +2057,7 @@ Inline::InlineBuiltInFunction(IR::Instr *callInstr, const FunctionJITTimeInfo * // Insert a byteCodeUsesInstr to make sure the function object's lifetime is extended beyond the last bailout point // at which we may need to call the inlinee again in the interpreter. - IR::ByteCodeUsesInstr * useCallTargetInstr = IR::ByteCodeUsesInstr::New(callInstr->GetPrevRealInstrOrLabel()); + useCallTargetInstr = IR::ByteCodeUsesInstr::New(callInstr->GetPrevRealInstrOrLabel()); useCallTargetInstr->SetRemovedOpndSymbol(originalCallTargetOpndIsJITOpt, originalCallTargetStackSym->m_id); if(inlineCallOpCode == Js::OpCode::InlineArrayPop) @@ -2364,7 +2350,7 @@ IR::Instr* Inline::InlineApply(IR::Instr *callInstr, const FunctionJITTimeInfo * // TODO: OOP JIT enable assert (readprocessmemory?) //Assert((inlineeData->GetFunctionInfo()->GetAttributes() & Js::FunctionInfo::Attributes::BuiltInInlinableAsLdFldInlinee) != 0); - return InlineApplyWithArray(callInstr, applyData, Js::JavascriptLibrary::GetBuiltInForFuncInfo(inlineeData->GetFunctionInfoAddr(), this->topFunc->GetThreadContextInfo())); + return InlineApplyBuiltInTargetWithArray(callInstr, applyData, inlineeData); } else { @@ -2477,7 +2463,7 @@ IR::Instr * Inline::InlineApplyWithArgumentsObject(IR::Instr * callInstr, IR::In /* This method will only do CallDirect style inlining of built-in targets. No script function inlining. */ -IR::Instr * Inline::InlineApplyWithArray(IR::Instr * callInstr, const FunctionJITTimeInfo * funcInfo, Js::BuiltinFunction builtInId) +IR::Instr * Inline::InlineApplyBuiltInTargetWithArray(IR::Instr * callInstr, const FunctionJITTimeInfo * applyInfo, const FunctionJITTimeInfo * builtInInfo) { IR::Instr * implicitThisArgOut = nullptr; IR::Instr * explicitThisArgOut = nullptr; @@ -2485,7 +2471,25 @@ IR::Instr * Inline::InlineApplyWithArray(IR::Instr * callInstr, const FunctionJI uint argOutCount = 0; this->GetArgInstrsForCallAndApply(callInstr, &implicitThisArgOut, &explicitThisArgOut, &arrayArgOut, argOutCount); - TryFixedMethodAndPrepareInsertionPoint(callInstr, funcInfo, false /*isPolymorphic*/, true /*isBuiltIn*/, false /*isCtor*/, true /*isInlined*/); + Js::OpCode originalCallOpcode = callInstr->m_opcode; + IR::Opnd * originalCallSrc1 = callInstr->GetSrc1()->Copy(this->topFunc); + IR::AutoReuseOpnd autoReuseOriginalCallSrc1(originalCallSrc1, this->topFunc); + + IR::Instr* applyLdInstr = nullptr; + IR::Instr* applyTargetLdInstr = nullptr; + if (!TryGetApplyAndTargetLdInstrs(callInstr, &applyLdInstr, &applyTargetLdInstr)) + { + return callInstr; + } + + // Fixed function/function object checks for target built-in + callInstr->ReplaceSrc1(applyTargetLdInstr->GetDst()); + EmitFixedMethodOrFunctionObjectChecksForBuiltIns(callInstr, callInstr, builtInInfo, false /*isPolymorphic*/, true /*isBuiltIn*/, false /*isCtor*/, true /*isInlined*/); + + // Fixed function/function object checks for .apply + callInstr->m_opcode = originalCallOpcode; + callInstr->ReplaceSrc1(originalCallSrc1); + EmitFixedMethodOrFunctionObjectChecksForBuiltIns(callInstr, callInstr, applyInfo, false /*isPolymorphic*/, true /*isBuiltIn*/, false /*isCtor*/, true /*isInlined*/); IR::Instr* builtInEndInstr = InsertInlineeBuiltInStartEndTags(callInstr, 3); // 3 args (implicit this + explicit this + array = 3) builtInEndInstr->m_opcode = Js::OpCode::InlineNonTrackingBuiltInEnd; // We will call EndTrackCall when we see CallDirect for reasons explained in GlobOpt::TrackCalls @@ -2513,6 +2517,7 @@ IR::Instr * Inline::InlineApplyWithArray(IR::Instr * callInstr, const FunctionJI argOut = IR::Instr::New(Js::OpCode::ArgOut_A_InlineSpecialized, linkOpnd, implicitThisArgOut->GetSrc1(), argOut->GetDst(), callInstr->m_func); callInstr->InsertBefore(argOut); + Js::BuiltinFunction builtInId = Js::JavascriptLibrary::GetBuiltInForFuncInfo(builtInInfo->GetFunctionInfoAddr(), this->topFunc->GetThreadContextInfo()); IR::HelperCallOpnd * helperCallOpnd = nullptr; switch (builtInId) { @@ -2543,7 +2548,7 @@ IR::Instr * Inline::InlineApplyWithoutArrayArgument(IR::Instr *callInstr, const uint argOutCount = 0; this->GetArgInstrsForCallAndApply(callInstr, &implicitThisArgOut, &explicitThisArgOut, &dummyInstr, argOutCount); - TryFixedMethodAndPrepareInsertionPoint(callInstr, applyInfo, false /*isPolymorphic*/, true /*isBuiltIn*/, false /*isCtor*/, true /*isInlined*/); + EmitFixedMethodOrFunctionObjectChecksForBuiltIns(callInstr, callInstr, applyInfo, false /*isPolymorphic*/, true /*isBuiltIn*/, false /*isCtor*/, true /*isInlined*/); InsertInlineeBuiltInStartEndTags(callInstr, 2); // 2 args (implicit this + explicit this) @@ -2616,6 +2621,22 @@ void Inline::GetArgInstrsForCallAndApply(IR::Instr* callInstr, IR::Instr** impli linkOpnd->AsRegOpnd()->m_sym->m_isInlinedArgSlot = true; } +bool Inline::TryGetApplyAndTargetLdInstrs(IR::Instr * callInstr, _Outptr_result_maybenull_ IR::Instr ** applyLdInstr, _Outptr_result_maybenull_ IR::Instr ** applyTargetLdInstr) +{ + IR::Opnd* applyOpnd = callInstr->GetSrc1(); + Assert(applyOpnd->IsRegOpnd()); + StackSym* applySym = applyOpnd->AsRegOpnd()->m_sym->AsStackSym(); + if (!applySym->IsSingleDef()) + { + *applyLdInstr = nullptr; + *applyTargetLdInstr = nullptr; + return false; + } + *applyLdInstr = applySym->GetInstrDef();; + *applyTargetLdInstr = (*applyLdInstr)->m_prev; + return true; +} + /* This method only inlines targets which are script functions, under the condition that the second argument (if any) passed to apply is arguments object. @@ -2637,16 +2658,13 @@ bool Inline::InlineApplyScriptTarget(IR::Instr *callInstr, const FunctionJITTime // Begin inlining apply target - IR::Opnd* applyOpnd = callInstr->GetSrc1(); - Assert(applyOpnd->IsRegOpnd()); - StackSym* applySym = applyOpnd->AsRegOpnd()->m_sym->AsStackSym(); - if (!applySym->IsSingleDef()) + IR::Instr* applyLdInstr = nullptr; + IR::Instr* applyTargetLdInstr = nullptr; + if (!TryGetApplyAndTargetLdInstrs(callInstr, &applyLdInstr, &applyTargetLdInstr)) { return false; } - IR::Instr* applyLdInstr = applySym->GetInstrDef(); - IR::Instr* applyTargetLdInstr = applyLdInstr->m_prev; - + if(applyTargetLdInstr->m_opcode != Js::OpCode::LdFldForCallApplyTarget || ((applyTargetLdInstr->AsProfiledInstr()->u.FldInfo().flags & Js::FldInfo_FromAccessor) != 0)) { @@ -2908,7 +2926,7 @@ Inline::InlineCall(IR::Instr *callInstr, const FunctionJITTimeInfo *funcInfo, co IR::SymOpnd* orgLinkOpnd = callInstr->GetSrc2()->AsSymOpnd(); - TryFixedMethodAndPrepareInsertionPoint(callInstr, funcInfo, false /*isPolymorphic*/, true /*isBuiltIn*/, false /*isCtor*/, true /*isInlined*/); + EmitFixedMethodOrFunctionObjectChecksForBuiltIns(callInstr, callInstr, funcInfo, false /*isPolymorphic*/, true /*isBuiltIn*/, false /*isCtor*/, true /*isInlined*/); InsertInlineeBuiltInStartEndTags(callInstr, actualCount); @@ -4225,26 +4243,29 @@ Inline::PrepareInsertionPoint(IR::Instr *callInstr, const FunctionJITTimeInfo *f return primaryBailOutInstr; } -void -Inline::TryFixedMethodAndPrepareInsertionPoint(IR::Instr *callInstr, const FunctionJITTimeInfo * inlineeInfo, bool isPolymorphic, bool isBuiltIn, bool isCtor, bool isInlined) +IR::ByteCodeUsesInstr* +Inline::EmitFixedMethodOrFunctionObjectChecksForBuiltIns(IR::Instr *callInstr, IR::Instr * funcObjCheckInsertInstr, const FunctionJITTimeInfo * inlineeInfo, bool isPolymorphic, bool isBuiltIn, bool isCtor, bool isInlined) { StackSym* originalCallTargetStackSym = callInstr->GetSrc1()->GetStackSym(); bool originalCallTargetIsJITOpt = callInstr->GetSrc1()->GetIsJITOptimizedReg(); + IR::ByteCodeUsesInstr * useCallTargetInstr = nullptr; bool safeThis = false; if (TryOptimizeCallInstrWithFixedMethod(callInstr, inlineeInfo, isPolymorphic, isBuiltIn, isCtor, isInlined, safeThis)) { Assert(callInstr->m_opcode == Js::OpCode::CallIFixed); - + Assert(callInstr->GetFixedFunction()->GetFuncInfoAddr() == inlineeInfo->GetFunctionInfoAddr()); // If we optimized the call instruction for a fixed function, we must extend the function object's lifetime until after the last bailout before the call. - IR::ByteCodeUsesInstr * useCallTargetInstr = IR::ByteCodeUsesInstr::New(callInstr); + useCallTargetInstr = IR::ByteCodeUsesInstr::New(callInstr); useCallTargetInstr->SetRemovedOpndSymbol(originalCallTargetIsJITOpt, originalCallTargetStackSym->m_id); callInstr->InsertBefore(useCallTargetInstr); } else { - PrepareInsertionPoint(callInstr, inlineeInfo, callInstr); + IR::BailOutInstr * bailOutInstr = IR::BailOutInstr::New(Js::OpCode::BailOnNotBuiltIn, IR::BailOutOnInlineFunction, callInstr, callInstr->m_func); + InsertFunctionObjectCheck(callInstr, funcObjCheckInsertInstr, bailOutInstr, inlineeInfo); } + return useCallTargetInstr; } uint Inline::CountActuals(IR::Instr *callInstr) diff --git a/lib/Backend/Inline.h b/lib/Backend/Inline.h index f4f00ceb348..e7e1ed58f91 100644 --- a/lib/Backend/Inline.h +++ b/lib/Backend/Inline.h @@ -47,13 +47,13 @@ class Inline IR::Instr * SimulateCallForGetterSetter(IR::Instr *accessorInstr, IR::Instr* insertInstr, IR::PropertySymOpnd* methodOpnd, bool isGetter); IR::Instr * InlineApply(IR::Instr *callInstr, const FunctionJITTimeInfo * applyData, const FunctionJITTimeInfo * inlinerData, const StackSym *symThis, bool* pIsInlined, uint callSiteId, uint recursiveInlineDepth, uint argsCount); - IR::Instr * InlineApplyWithArray(IR::Instr *callInstr, const FunctionJITTimeInfo * inlineeInfo, Js::BuiltinFunction builtInId); + IR::Instr * InlineApplyBuiltInTargetWithArray(IR::Instr *callInstr, const FunctionJITTimeInfo * applyInfo, const FunctionJITTimeInfo * builtInInfo); IR::Instr * InlineApplyWithArgumentsObject(IR::Instr * callInstr, IR::Instr * argsObjectArgInstr, const FunctionJITTimeInfo * inlineeInfo); IR::Instr * InlineApplyWithoutArrayArgument(IR::Instr *callInstr, const FunctionJITTimeInfo * applyInfo, const FunctionJITTimeInfo * applyTargetInfo); bool InlineApplyScriptTarget(IR::Instr *callInstr, const FunctionJITTimeInfo* inlinerData, const FunctionJITTimeInfo** pInlineeData, const FunctionJITTimeInfo * applyFuncInfo, const StackSym *symThis, IR::Instr ** returnInstr, uint recursiveInlineDepth, bool isArrayOpndArgumentsObject, uint argsCount); void GetArgInstrsForCallAndApply(IR::Instr* callInstr, IR::Instr** implicitThisArgOut, IR::Instr** explicitThisArgOut, IR::Instr** argumentsOrArrayArgOut, uint &argOutCount); - + bool TryGetApplyAndTargetLdInstrs(IR::Instr * callInstr, _Outptr_result_maybenull_ IR::Instr ** applyLdInstr, _Outptr_result_maybenull_ IR::Instr ** applyTargetLdInstr); IR::Instr * InlineCall(IR::Instr *callInstr, const FunctionJITTimeInfo * inlineeData, const FunctionJITTimeInfo * inlinerData, const StackSym *symThis, bool* pIsInlined, uint callSiteId, uint recursiveInlineDepth); bool InlineCallTarget(IR::Instr *callInstr, const FunctionJITTimeInfo* inlinerData, const FunctionJITTimeInfo** pInlineeData, const FunctionJITTimeInfo *callFuncInfo, const StackSym *symThis, IR::Instr ** returnInstr, uint recursiveInlineDepth); @@ -83,7 +83,7 @@ class Inline void FixupExtraActualParams(IR::Instr * instr, IR::Instr *argOuts[], IR::Instr *argOutsExtra[], uint index, uint actualCount, Js::ProfileId callSiteId); void RemoveExtraFixupArgouts(IR::Instr* instr, uint argoutRemoveCount, Js::ProfileId callSiteId); IR::Instr* PrepareInsertionPoint(IR::Instr *callInstr, const FunctionJITTimeInfo *funcInfo, IR::Instr *insertBeforeInstr, IR::BailOutKind bailOutKind = IR::BailOutOnInlineFunction); - void TryFixedMethodAndPrepareInsertionPoint(IR::Instr *callInstr, const FunctionJITTimeInfo * inlineeInfo, bool isPolymorphic, bool isBuiltIn, bool isCtor, bool isInlined); + IR::ByteCodeUsesInstr* EmitFixedMethodOrFunctionObjectChecksForBuiltIns(IR::Instr *callInstr, IR::Instr * funcObjCheckInsertInstr, const FunctionJITTimeInfo * inlineeInfo, bool isPolymorphic, bool isBuiltIn, bool isCtor, bool isInlined); Js::ArgSlot MapActuals(IR::Instr *callInstr, __out_ecount(maxParamCount) IR::Instr *argOuts[], Js::ArgSlot formalCount, Func *inlinee, Js::ProfileId callSiteId, bool *stackArgsArgOutExpanded, IR::Instr *argOutsExtra[] = nullptr, Js::ArgSlot maxParamCount = Js::InlineeCallInfo::MaxInlineeArgoutCount); uint32 CountActuals(IR::Instr *callIntr); void MapFormals(Func *inlinee, __in_ecount(formalCount) IR::Instr *argOuts[], uint formalCount, uint actualCount, IR::RegOpnd *retOpnd, IR::Opnd * funcObjOpnd, const StackSym *symCallerThis, bool stackArgsArgOutExpanded, bool fixedFunctionSafeThis = false, IR::Instr *argOutsExtra[] = nullptr);