Skip to content

Commit f8bdb18

Browse files
sethbrenithaneeshdk
authored andcommitted
[CVE-2018-8359] Edge - Chakra OOB Write on ProxyEntryPointInfo - Internal
1 parent 15bd399 commit f8bdb18

16 files changed

+185
-85
lines changed

lib/Backend/BailOut.cpp

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,13 @@ uint32 BailOutRecord::GetArgumentsObjectOffset()
506506
return argumentsObjectOffset;
507507
}
508508

509+
Js::FunctionEntryPointInfo *BailOutRecord::GetFunctionEntryPointInfo() const
510+
{
511+
Js::EntryPointInfo* result = this->globalBailOutRecordTable->entryPointInfo;
512+
AssertOrFailFast(result->IsFunctionEntryPointInfo());
513+
return (Js::FunctionEntryPointInfo*)result;
514+
}
515+
509516
Js::Var BailOutRecord::EnsureArguments(Js::InterpreterStackFrame * newInstance, Js::JavascriptCallStackLayout * layout, Js::ScriptContext* scriptContext, Js::Var* pArgumentsObject) const
510517
{
511518
Assert(globalBailOutRecordTable->hasStackArgOpt);
@@ -1706,7 +1713,27 @@ void BailOutRecord::ScheduleFunctionCodeGen(Js::ScriptFunction * function, Js::S
17061713
BailOutRecord * bailOutRecordNotConst = (BailOutRecord *)(void *)bailOutRecord;
17071714
bailOutRecordNotConst->bailOutCount++;
17081715

1709-
Js::FunctionEntryPointInfo *entryPointInfo = function->GetFunctionEntryPointInfo();
1716+
Js::FunctionEntryPointInfo *entryPointInfo = bailOutRecord->GetFunctionEntryPointInfo();
1717+
1718+
#if DBG
1719+
// BailOutRecord is not recycler-allocated, so make sure something the recycler can see was keeping the entry point info alive.
1720+
// We expect the entry point to be kept alive as follows:
1721+
// 1. The function's current type might still have the same entry point info as when we entered the function (easy case)
1722+
// 2. The function might have moved to a successor path type, which still keeps the previous type and its entry point info alive
1723+
// 3. The entry point info might be held by the ThreadContext (QueueFreeOldEntryPointInfoIfInScript):
1724+
// a. If the entry point info was replaced on the type that used to hold it (ScriptFunction::ChangeEntryPoint)
1725+
// b. If the function's last-added property was deleted and it moved to a previous type (ScriptFunction::ReplaceTypeWithPredecessorType)
1726+
// c. If the function's path type got replaced with a dictionary, then all previous entry point infos in that path are queued on the ThreadContext (ScriptFunction::PrepareForConversionToNonPathType)
1727+
bool foundEntryPoint = false;
1728+
executeFunction->MapEntryPointsUntil([&](int index, Js::FunctionEntryPointInfo* info)
1729+
{
1730+
foundEntryPoint = info == entryPointInfo;
1731+
return foundEntryPoint;
1732+
});
1733+
foundEntryPoint = foundEntryPoint || function->GetScriptContext()->GetThreadContext()->IsOldEntryPointInfo(entryPointInfo);
1734+
Assert(foundEntryPoint);
1735+
#endif
1736+
17101737
uint8 callsCount = entryPointInfo->callsCount > 255 ? 255 : static_cast<uint8>(entryPointInfo->callsCount);
17111738
RejitReason rejitReason = RejitReason::None;
17121739
bool reThunk = false;
@@ -2235,7 +2262,7 @@ void BailOutRecord::ScheduleFunctionCodeGen(Js::ScriptFunction * function, Js::S
22352262
if (bailOutRecord->IsForLoopTop() && IR::IsTypeCheckBailOutKind(bailOutRecord->bailOutKind))
22362263
{
22372264
// Disable FieldPRE if we're triggering a type check rejit due to a bailout at the loop top.
2238-
// Most likely this was caused by a CheckFixedFld that was hoisted from a branch block where
2265+
// Most likely this was caused by a CheckFixedFld that was hoisted from a branch block where
22392266
// only certain types flowed, to the loop top, where more types (different or non-equivalent)
22402267
// were flowing in.
22412268
profileInfo->DisableFieldPRE();

lib/Backend/BailOut.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,8 @@ class BailOutRecord
209209
void SetType(BailoutRecordType type) { this->type = type; }
210210
bool IsShared() const { return type == Shared || type == SharedForLoopTop; }
211211
bool IsForLoopTop() const { return type == SharedForLoopTop; }
212+
213+
Js::FunctionEntryPointInfo *GetFunctionEntryPointInfo() const;
212214
protected:
213215
struct BailOutReturnValue
214216
{
@@ -237,7 +239,7 @@ class BailOutRecord
237239

238240
static void UpdatePolymorphicFieldAccess(Js::JavascriptFunction * function, BailOutRecord const * bailOutRecord);
239241

240-
static void ScheduleFunctionCodeGen(Js::ScriptFunction * function, Js::ScriptFunction * innerMostInlinee, BailOutRecord const * bailOutRecord, IR::BailOutKind bailOutKind,
242+
static void ScheduleFunctionCodeGen(Js::ScriptFunction * function, Js::ScriptFunction * innerMostInlinee, BailOutRecord const * bailOutRecord, IR::BailOutKind bailOutKind,
241243
uint32 actualBailOutOffset, Js::ImplicitCallFlags savedImplicitCallFlags, void * returnAddress);
242244
static void ScheduleLoopBodyCodeGen(Js::ScriptFunction * function, Js::ScriptFunction * innerMostInlinee, BailOutRecord const * bailOutRecord, IR::BailOutKind bailOutKind);
243245
static void CheckPreemptiveRejit(Js::FunctionBody* executeFunction, IR::BailOutKind bailOutKind, BailOutRecord* bailoutRecord, uint8& callsOrIterationsCount, int loopNumber);
@@ -416,6 +418,7 @@ struct GlobalBailOutRecordDataTable
416418
// The offset to 'registerSaveSpace' is hard-coded in LinearScanMD::SaveAllRegisters, so let this be the first member variable
417419
Js::Var *registerSaveSpace;
418420
GlobalBailOutRecordDataRow *globalBailOutRecordDataRows;
421+
Js::EntryPointInfo *entryPointInfo;
419422
uint32 length;
420423
uint32 size;
421424
int32 firstActualStackOffset;

lib/Backend/FunctionJITTimeInfo.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ FunctionJITTimeInfo::BuildJITTimeData(
2727
jitData->isInlined = codeGenData->GetIsInlined();
2828
jitData->weakFuncRef = (intptr_t)codeGenData->GetWeakFuncRef();
2929
jitData->inlineesBv = (BVFixedIDL*)(const BVFixed*)codeGenData->inlineesBv;
30+
jitData->entryPointInfoAddr = (intptr_t)codeGenData->GetEntryPointInfo();
3031

3132
if (!codeGenData->GetFunctionBody() || !codeGenData->GetFunctionBody()->GetByteCode())
3233
{
@@ -62,7 +63,7 @@ FunctionJITTimeInfo::BuildJITTimeData(
6263
Assert(defaultEntryPointInfo->IsFunctionEntryPointInfo());
6364
Js::FunctionEntryPointInfo *functionEntryPointInfo = static_cast<Js::FunctionEntryPointInfo*>(defaultEntryPointInfo);
6465
jitData->callsCountAddress = (intptr_t)&functionEntryPointInfo->callsCount;
65-
66+
6667
jitData->sharedPropertyGuards = codeGenData->sharedPropertyGuards;
6768
jitData->sharedPropGuardCount = codeGenData->sharedPropertyGuardCount;
6869
}
@@ -203,6 +204,12 @@ FunctionJITTimeInfo::GetFunctionInfoAddr() const
203204
return m_data.functionInfoAddr;
204205
}
205206

207+
intptr_t
208+
FunctionJITTimeInfo::GetEntryPointInfoAddr() const
209+
{
210+
return m_data.entryPointInfoAddr;
211+
}
212+
206213
intptr_t
207214
FunctionJITTimeInfo::GetWeakFuncRef() const
208215
{

lib/Backend/FunctionJITTimeInfo.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class FunctionJITTimeInfo
2727
JITTimeFunctionBody * GetBody() const;
2828
bool IsPolymorphicCallSite(Js::ProfileId profiledCallSiteId) const;
2929
intptr_t GetFunctionInfoAddr() const;
30+
intptr_t GetEntryPointInfoAddr() const;
3031
intptr_t GetWeakFuncRef() const;
3132
uint GetLocalFunctionId() const;
3233
uint GetSourceContextId() const;

lib/Backend/LinearScan.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1288,6 +1288,7 @@ LinearScan::EnsureGlobalBailOutRecordTable(Func *func)
12881288
if (globalBailOutRecordDataTable == nullptr)
12891289
{
12901290
globalBailOutRecordDataTable = globalBailOutRecordTables[inlineeID] = NativeCodeDataNew(allocator, GlobalBailOutRecordDataTable);
1291+
globalBailOutRecordDataTable->entryPointInfo = (Js::EntryPointInfo*)func->GetWorkItem()->GetJITTimeInfo()->GetEntryPointInfoAddr();
12911292
globalBailOutRecordDataTable->length = globalBailOutRecordDataTable->size = 0;
12921293
globalBailOutRecordDataTable->isInlinedFunction = !isTopFunc;
12931294
globalBailOutRecordDataTable->hasNonSimpleParams = func->GetHasNonSimpleParams();
@@ -2603,14 +2604,14 @@ LinearScan::FindReg(Lifetime *newLifetime, IR::RegOpnd *regOpnd, bool force)
26032604
// Avoid the temp reg that we have loaded in this basic block
26042605
regsBvNoTemps.Minus(this->tempRegs);
26052606
}
2606-
2607+
26072608
BitVector regsBvNoTempsNoCallee = regsBvNoTemps;
26082609
// Try to find a non-callee saved reg so that we don't have to save it in prolog
26092610
regsBvNoTempsNoCallee.Minus(this->calleeSavedRegs);
26102611

26112612
// Allocate a non-callee saved reg from the other end of the bit vector so that it can keep live for longer
26122613
regIndex = regsBvNoTempsNoCallee.GetPrevBit();
2613-
2614+
26142615
if (regIndex == BVInvalidIndex)
26152616
{
26162617
// If we don't have any non-callee saved reg then get the first available callee saved reg so that prolog can store adjacent registers
@@ -3730,7 +3731,7 @@ LinearScan::ProcessSecondChanceBoundaryHelper(IR::BranchInstr *branchInstr, IR::
37303731
}
37313732
else
37323733
{
3733-
// Dead code after the unconditional branch causes the currentBlock data to be freed later on...
3734+
// Dead code after the unconditional branch causes the currentBlock data to be freed later on...
37343735
// Deep copy in this case.
37353736
branchLabel->m_loweredBasicBlock = this->currentBlock->Clone(this->tempAlloc);
37363737
}
@@ -4730,7 +4731,7 @@ IR::Instr * LinearScan::GetIncInsertionPoint(IR::Instr *instr)
47304731
}
47314732

47324733
void LinearScan::DynamicStatsInstrument()
4733-
{
4734+
{
47344735
{
47354736
IR::Instr *firstInstr = this->func->m_headInstr;
47364737
IR::MemRefOpnd *memRefOpnd = IR::MemRefOpnd::New(this->func->GetJITFunctionBody()->GetCallCountStatsAddr(), TyUint32, this->func);

lib/JITIDL/JITTypes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,7 @@ typedef struct FunctionJITTimeDataIDL
680680
CHAKRA_PTR functionInfoAddr;
681681
CHAKRA_PTR callsCountAddress;
682682
CHAKRA_PTR weakFuncRef;
683+
CHAKRA_PTR entryPointInfoAddr;
683684
} FunctionJITTimeDataIDL;
684685

685686
#if !FLOATVAR

lib/Runtime/Base/FunctionBody.cpp

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2088,33 +2088,6 @@ namespace Js
20882088
this->SetAuxPtr<AuxPointerType::FunctionObjectTypeList>(list);
20892089
}
20902090

2091-
template <typename Fn>
2092-
void FunctionProxy::MapFunctionObjectTypes(Fn func)
2093-
{
2094-
FunctionTypeWeakRefList* functionObjectTypeList = this->GetFunctionObjectTypeList();
2095-
if (functionObjectTypeList != nullptr)
2096-
{
2097-
functionObjectTypeList->Map([&](int, FunctionTypeWeakRef* typeWeakRef)
2098-
{
2099-
if (typeWeakRef)
2100-
{
2101-
ScriptFunctionType* type = typeWeakRef->Get();
2102-
if (type)
2103-
{
2104-
func(type);
2105-
}
2106-
}
2107-
});
2108-
}
2109-
2110-
if (this->deferredPrototypeType)
2111-
{
2112-
func(this->deferredPrototypeType);
2113-
}
2114-
// NOTE: We deliberately do not map the undeferredFunctionType here, since it's in the list
2115-
// of registered function object types we processed above.
2116-
}
2117-
21182091
FunctionProxy::FunctionTypeWeakRefList* FunctionProxy::EnsureFunctionObjectTypeList()
21192092
{
21202093
FunctionTypeWeakRefList* functionObjectTypeList = this->GetFunctionObjectTypeList();

lib/Runtime/Base/FunctionBody.h

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ namespace Js
222222
// main and JIT threads.
223223
class EntryPointInfo : public ProxyEntryPointInfo
224224
{
225-
225+
226226
private:
227227
enum State : BYTE
228228
{
@@ -296,7 +296,6 @@ namespace Js
296296

297297
public:
298298
virtual void Finalize(bool isShutdown) override;
299-
virtual bool IsFunctionEntryPointInfo() const override { return true; }
300299

301300
#if ENABLE_NATIVE_CODEGEN
302301
NativeEntryPointData * EnsureNativeEntryPointData();
@@ -572,9 +571,9 @@ namespace Js
572571
void ResetOnLazyBailoutFailure();
573572
void OnNativeCodeInstallFailure();
574573
virtual void ResetOnNativeCodeInstallFailure() = 0;
575-
576-
void FreeJitTransferData();
577-
bool ClearEquivalentTypeCaches();
574+
575+
void FreeJitTransferData();
576+
bool ClearEquivalentTypeCaches();
578577

579578
virtual void Invalidate(bool prolongEntryPoint) { Assert(false); }
580579
InlineeFrameRecord* FindInlineeFrame(void* returnAddress);
@@ -624,6 +623,8 @@ namespace Js
624623
public:
625624
FunctionEntryPointInfo(FunctionProxy * functionInfo, Js::JavascriptMethod method, ThreadContext* context);
626625

626+
virtual bool IsFunctionEntryPointInfo() const override { return true; }
627+
627628
bool ExecutedSinceCallCountCollection() const;
628629
void CollectCallCounts();
629630

@@ -1063,8 +1064,33 @@ namespace Js
10631064
FunctionTypeWeakRefList* GetFunctionObjectTypeList() const;
10641065
void SetFunctionObjectTypeList(FunctionTypeWeakRefList* list);
10651066
void RegisterFunctionObjectType(ScriptFunctionType* functionType);
1067+
10661068
template <typename Fn>
1067-
void MapFunctionObjectTypes(Fn func);
1069+
void MapFunctionObjectTypes(Fn func)
1070+
{
1071+
FunctionTypeWeakRefList* functionObjectTypeList = this->GetFunctionObjectTypeList();
1072+
if (functionObjectTypeList != nullptr)
1073+
{
1074+
functionObjectTypeList->Map([&](int, FunctionTypeWeakRef* typeWeakRef)
1075+
{
1076+
if (typeWeakRef)
1077+
{
1078+
ScriptFunctionType* type = typeWeakRef->Get();
1079+
if (type)
1080+
{
1081+
func(type);
1082+
}
1083+
}
1084+
});
1085+
}
1086+
1087+
if (this->deferredPrototypeType)
1088+
{
1089+
func(this->deferredPrototypeType);
1090+
}
1091+
// NOTE: We deliberately do not map the undeferredFunctionType here, since it's in the list
1092+
// of registered function object types we processed above.
1093+
}
10681094

10691095
static uint GetOffsetOfDeferredPrototypeType() { return static_cast<uint>(offsetof(Js::FunctionProxy, deferredPrototypeType)); }
10701096
static Js::ScriptFunctionType * EnsureFunctionProxyDeferredPrototypeType(FunctionProxy * proxy)
@@ -2196,7 +2222,7 @@ namespace Js
21962222
#if DYNAMIC_INTERPRETER_THUNK
21972223
void GenerateDynamicInterpreterThunk();
21982224
#endif
2199-
2225+
22002226
Js::JavascriptMethod GetEntryPoint(ProxyEntryPointInfo* entryPoint) const { return entryPoint->jsMethod; }
22012227
void CaptureDynamicProfileState(FunctionEntryPointInfo* entryPointInfo);
22022228
#if ENABLE_DEBUG_CONFIG_OPTIONS

lib/Runtime/Base/ThreadContext.cpp

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -721,7 +721,7 @@ bool ThreadContext::ThreadContextRecyclerTelemetryHostInterface::TransmitTelemet
721721

722722
bool ThreadContext::ThreadContextRecyclerTelemetryHostInterface::IsThreadBound() const
723723
{
724-
return this->tc->IsThreadBound();
724+
return this->tc->IsThreadBound();
725725
}
726726

727727

@@ -2641,11 +2641,16 @@ ThreadContext::DoExpirableCollectModeStackWalk()
26412641
if (javascriptFunction != nullptr && Js::ScriptFunction::Test(javascriptFunction))
26422642
{
26432643
Js::ScriptFunction* scriptFunction = (Js::ScriptFunction*) javascriptFunction;
2644-
Js::FunctionEntryPointInfo* entryPointInfo = scriptFunction->GetFunctionEntryPointInfo();
2645-
entryPointInfo->SetIsObjectUsed();
2644+
26462645
scriptFunction->GetFunctionBody()->MapEntryPoints([](int index, Js::FunctionEntryPointInfo* entryPoint){
26472646
entryPoint->SetIsObjectUsed();
26482647
});
2648+
2649+
// Make sure we marked the current one when iterating all entry points
2650+
Js::ProxyEntryPointInfo* entryPointInfo = scriptFunction->GetEntryPointInfo();
2651+
Assert(entryPointInfo == nullptr
2652+
|| !entryPointInfo->IsFunctionEntryPointInfo()
2653+
|| ((Js::FunctionEntryPointInfo*)entryPointInfo)->IsObjectUsed());
26492654
}
26502655
}
26512656
}

lib/Runtime/Base/ThreadContext.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1466,6 +1466,18 @@ class ThreadContext sealed :
14661466
}
14671467
}
14681468

1469+
bool IsOldEntryPointInfo(Js::ProxyEntryPointInfo* entryPointInfo)
1470+
{
1471+
Js::FunctionEntryPointInfo* current = this->recyclableData->oldEntryPointInfo;
1472+
while (current != nullptr)
1473+
{
1474+
if (current == entryPointInfo)
1475+
return true;
1476+
current = current->nextEntryPoint;
1477+
}
1478+
return false;
1479+
}
1480+
14691481
static bool IsOnStack(void const *ptr);
14701482
_NOINLINE bool IsStackAvailable(size_t size, bool* isInterrupt = nullptr);
14711483
_NOINLINE bool IsStackAvailableNoThrow(size_t size = Js::Constants::MinStackDefault);

0 commit comments

Comments
 (0)