Skip to content

Commit

Permalink
[CVE-2018-8128] Edge - UAF of borrowed inline caches after redeferral
Browse files Browse the repository at this point in the history
Do not allow ScriptFunctionWithInlineCache to borrow its InlineCache* from the FunctionBody, because in certain cases of redeferral this will cause jitted code to access stale pointers. Instead of borrowing the caches from the FunctionBody, create a base ScriptFunction and let the runtime access the current FunctionBody inline caches when the function is executed.
  • Loading branch information
pleath authored and MSLaguana committed May 8, 2018
1 parent c8f723d commit 23848b1
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 102 deletions.
20 changes: 0 additions & 20 deletions lib/Runtime/Library/JavascriptFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1601,16 +1601,8 @@ void __cdecl _alloca_probe_16()

Assert(functionInfo);

ScriptFunctionWithInlineCache * funcObjectWithInlineCache = ScriptFunctionWithInlineCache::Is(*functionRef) ? ScriptFunctionWithInlineCache::FromVar(*functionRef) : nullptr;
if (functionInfo->IsDeferredParseFunction())
{
if (funcObjectWithInlineCache)
{
// If inline caches were populated from a function body that has been redeferred, the caches have been cleaned up,
// so clear the pointers. REVIEW: Is this a perf loss in some cases?
funcObjectWithInlineCache->ClearBorrowedInlineCacheOnFunctionObject();
}

funcBody = functionInfo->Parse(functionRef);
fParsed = funcBody->IsFunctionParsed() ? TRUE : FALSE;

Expand All @@ -1636,18 +1628,6 @@ void __cdecl _alloca_probe_16()

JavascriptMethod thunkEntryPoint = (*functionRef)->UpdateUndeferredBody(funcBody);

if (funcObjectWithInlineCache && !funcObjectWithInlineCache->GetHasOwnInlineCaches())
{
// If the function object needs to use the inline caches from the function body, point them to the
// function body's caches. This is required in two redeferral cases:
//
// 1. We might have cleared the caches on the function object (ClearBorrowedInlineCacheOnFunctionObject)
// above if the function body was redeferred.
// 2. Another function object could have been called before and undeferred the function body, thereby creating
// new inline caches. This function object would still be pointing to the old ones and needs updating.
funcObjectWithInlineCache->SetInlineCachesFromFunctionBody();
}

return thunkEntryPoint;
}

Expand Down
102 changes: 25 additions & 77 deletions lib/Runtime/Library/ScriptFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,50 +83,47 @@ namespace Js

bool hasSuperReference = functionProxy->HasSuperReference();

ScriptFunction * pfuncScript = nullptr;

if (functionProxy->IsFunctionBody() && functionProxy->GetFunctionBody()->GetInlineCachesOnFunctionObject())
{
Js::FunctionBody * functionBody = functionProxy->GetFunctionBody();
ScriptFunctionWithInlineCache* pfuncScriptWithInlineCache = scriptContext->GetLibrary()->CreateScriptFunctionWithInlineCache(functionProxy);
pfuncScriptWithInlineCache->SetEnvironment(environment);
JS_ETW(EventWriteJSCRIPT_RECYCLER_ALLOCATE_FUNCTION(pfuncScriptWithInlineCache, EtwTrace::GetFunctionId(functionProxy)));

Assert(functionBody->GetInlineCacheCount() + functionBody->GetIsInstInlineCacheCount());

FunctionBody * functionBody = functionProxy->GetFunctionBody();
if (functionBody->GetIsFirstFunctionObject())
{
// point the inline caches of the first function object to those on the function body.
pfuncScriptWithInlineCache->SetInlineCachesFromFunctionBody();
functionBody->SetIsNotFirstFunctionObject();
}
else
{
ScriptFunctionWithInlineCache* pfuncScriptWithInlineCache = scriptContext->GetLibrary()->CreateScriptFunctionWithInlineCache(functionProxy);
// allocate inline cache for this function object
pfuncScriptWithInlineCache->CreateInlineCache();
}

pfuncScriptWithInlineCache->SetHasSuperReference(hasSuperReference);
Assert(functionBody->GetInlineCacheCount() + functionBody->GetIsInstInlineCacheCount());
if (PHASE_TRACE1(Js::ScriptFunctionWithInlineCachePhase))
{
char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];

if (PHASE_TRACE1(Js::ScriptFunctionWithInlineCachePhase))
{
char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
Output::Print(_u("Function object with inline cache: function number: (%s)\tfunction name: %s\n"),
functionBody->GetDebugNumberSet(debugStringBuffer), functionBody->GetDisplayName());
Output::Flush();
}

Output::Print(_u("Function object with inline cache: function number: (%s)\tfunction name: %s\n"),
functionBody->GetDebugNumberSet(debugStringBuffer), functionBody->GetDisplayName());
Output::Flush();
pfuncScript = pfuncScriptWithInlineCache;
}
return pfuncScriptWithInlineCache;
}
else

if (pfuncScript == nullptr)
{
ScriptFunction* pfuncScript = scriptContext->GetLibrary()->CreateScriptFunction(functionProxy);
pfuncScript->SetEnvironment(environment);
pfuncScript = scriptContext->GetLibrary()->CreateScriptFunction(functionProxy);
}

pfuncScript->SetHasSuperReference(hasSuperReference);
pfuncScript->SetEnvironment(environment);

JS_ETW(EventWriteJSCRIPT_RECYCLER_ALLOCATE_FUNCTION(pfuncScript, EtwTrace::GetFunctionId(functionProxy)));
pfuncScript->SetHasSuperReference(hasSuperReference);

return pfuncScript;
}
JS_ETW(EventWriteJSCRIPT_RECYCLER_ALLOCATE_FUNCTION(pfuncScript, EtwTrace::GetFunctionId(functionProxy)));

return pfuncScript;
}

void ScriptFunction::SetEnvironment(FrameDisplay * environment)
Expand Down Expand Up @@ -749,11 +746,11 @@ namespace Js
#endif

ScriptFunctionWithInlineCache::ScriptFunctionWithInlineCache(FunctionProxy * proxy, ScriptFunctionType* deferredPrototypeType) :
ScriptFunction(proxy, deferredPrototypeType), hasOwnInlineCaches(false)
ScriptFunction(proxy, deferredPrototypeType)
{}

ScriptFunctionWithInlineCache::ScriptFunctionWithInlineCache(DynamicType * type) :
ScriptFunction(type), hasOwnInlineCaches(false)
ScriptFunction(type)
{}

bool ScriptFunctionWithInlineCache::Is(Var func)
Expand Down Expand Up @@ -786,45 +783,6 @@ namespace Js
return reinterpret_cast<InlineCache *>(PointerValue(inlineCaches[index]));
}

Field(void**) ScriptFunctionWithInlineCache::GetInlineCaches()
{
// If script function have inline caches pointing to function body and function body got reparsed we need to reset cache
if (this->GetHasInlineCaches() && !this->GetHasOwnInlineCaches())
{
// Script function have inline caches pointing to function body
if (!this->HasFunctionBody())
{
// Function body got re-deferred and have not been re-parsed yet. Reset cache to null
this->m_inlineCaches = nullptr;
this->inlineCacheCount = 0;
this->SetHasInlineCaches(false);
}
else if (this->m_inlineCaches != this->GetFunctionBody()->GetInlineCaches())
{
// Function body got reparsed we need to reset cache
Assert(this->GetFunctionBody()->GetCompileCount() > 1);
this->SetInlineCachesFromFunctionBody();
}
}

return this->m_inlineCaches;
}

void ScriptFunctionWithInlineCache::SetInlineCachesFromFunctionBody()
{
SetHasInlineCaches(true);
Js::FunctionBody* functionBody = this->GetFunctionBody();
this->m_inlineCaches = functionBody->GetInlineCaches();
#if DBG
this->m_inlineCacheTypes = functionBody->GetInlineCacheTypes();
#endif
this->rootObjectLoadInlineCacheStart = functionBody->GetRootObjectLoadInlineCacheStart();
this->rootObjectLoadMethodInlineCacheStart = functionBody->GetRootObjectLoadMethodInlineCacheStart();
this->rootObjectStoreInlineCacheStart = functionBody->GetRootObjectStoreInlineCacheStart();
this->inlineCacheCount = functionBody->GetInlineCacheCount();
this->isInstInlineCacheCount = functionBody->GetIsInstInlineCacheCount();
}

void ScriptFunctionWithInlineCache::CreateInlineCache()
{
Js::FunctionBody *functionBody = this->GetFunctionBody();
Expand All @@ -835,7 +793,6 @@ namespace Js

SetHasInlineCaches(true);
AllocateInlineCache();
hasOwnInlineCaches = true;
}

void ScriptFunctionWithInlineCache::Finalize(bool isShutdown)
Expand All @@ -854,7 +811,7 @@ namespace Js
{
uint isInstInlineCacheStart = this->GetInlineCacheCount();
uint totalCacheCount = isInstInlineCacheStart + isInstInlineCacheCount;
if (this->GetHasInlineCaches() && this->m_inlineCaches && this->hasOwnInlineCaches)
if (this->GetHasInlineCaches() && this->m_inlineCaches)
{
Js::ScriptContext* scriptContext = this->GetParseableFunctionInfo()->GetScriptContext();
uint i = 0;
Expand Down Expand Up @@ -1087,13 +1044,4 @@ namespace Js
}
SetHasInlineCaches(false);
}

void ScriptFunctionWithInlineCache::ClearBorrowedInlineCacheOnFunctionObject()
{
if (this->hasOwnInlineCaches)
{
return;
}
ClearInlineCacheOnFunctionObject();
}
}
6 changes: 1 addition & 5 deletions lib/Runtime/Library/ScriptFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,6 @@ namespace Js
{
private:
Field(void**) m_inlineCaches;
Field(bool) hasOwnInlineCaches;

#if DBG
#define InlineCacheTypeNone 0x00
Expand Down Expand Up @@ -212,12 +211,9 @@ namespace Js
void CreateInlineCache();
void AllocateInlineCache();
void ClearInlineCacheOnFunctionObject();
void ClearBorrowedInlineCacheOnFunctionObject();
InlineCache * GetInlineCache(uint index);
uint GetInlineCacheCount() { return inlineCacheCount; }
Field(void**) GetInlineCaches();
bool GetHasOwnInlineCaches() { return hasOwnInlineCaches; }
void SetInlineCachesFromFunctionBody();
Field(void**) GetInlineCaches() const { return m_inlineCaches; }
static uint32 GetOffsetOfInlineCaches() { return offsetof(ScriptFunctionWithInlineCache, m_inlineCaches); };
template<bool isShutdown>
void FreeOwnInlineCaches();
Expand Down

0 comments on commit 23848b1

Please sign in to comment.