Permalink
Browse files

[CVE-2018-8367] Edge - UAF for Edge on WIP - Qihoo 360

This change fixes an issue where deepCopied arrays have a different inlined head segment behavior from its original instance. This is because deepCopy'ing an array would unconditionally allocate the head segment as inline, regardless of its size. This conflicts with JavascriptArray::HasInlineHeadSegment, which is based upon size rather than layout. In the PoC, this causes problems when removing elements via Slice.
The fix is to clone the inlined behavior of the original instance in addition to the instance's data. This allows related invariants to be maintained.

This change also ensures that the aligned, allocated size of the inline head segment on the stack is never larger than INLINE_CHUNK_SIZE, similarly to NewLiteral.
  • Loading branch information...
Thomas Moore (CHAKRA) authored and MikeHolman committed Jul 27, 2018
1 parent e035a2d commit dd5b2e75e7aebe67b5185383080c0648f5353ea0
Showing with 57 additions and 17 deletions.
  1. +10 −0 lib/Backend/Lower.cpp
  2. +46 −16 lib/Runtime/Library/JavascriptArray.cpp
  3. +1 −1 lib/Runtime/Library/JavascriptArray.h
@@ -3880,6 +3880,16 @@ Lowerer::GenerateArrayAllocHelper(IR::Instr *instr, uint32 * psize, Js::ArrayCal
uint32 allocCount = count == 0 ? Js::SparseArraySegmentBase::SMALL_CHUNK_SIZE : count;
arrayAllocSize = Js::JavascriptArray::DetermineAllocationSize<ArrayType, 0>(allocCount, nullptr, &alignedHeadSegmentSize);
}

// Note that it is possible for the returned alignedHeadSegmentSize to be greater than INLINE_CHUNK_SIZE because
// of rounding the *entire* object, including the head segment, to the nearest aligned size. In that case, ensure
// that this size is still not larger than INLINE_CHUNK_SIZE size because the head segment is still inlined. This
// keeps consistency with the definition of HasInlineHeadSegment and maintained in the assert below.
uint inlineChunkSize = Js::SparseArraySegmentBase::INLINE_CHUNK_SIZE;
alignedHeadSegmentSize = min(alignedHeadSegmentSize, inlineChunkSize);

Assert(ArrayType::HasInlineHeadSegment(alignedHeadSegmentSize));

leaHeadInstr = IR::Instr::New(Js::OpCode::LEA, headOpnd,
IR::IndirOpnd::New(dstOpnd, sizeof(ArrayType), TyMachPtr, func), func);
isHeadSegmentZeroed = true;
@@ -11814,24 +11814,42 @@ using namespace Js;
#endif

template <typename T>
void JavascriptArray::InitBoxedInlineSegments(SparseArraySegment<T> * dst, SparseArraySegment<T> * src, bool deepCopy)
void JavascriptArray::InitBoxedInlineSegments(T * instance, bool deepCopy)
{
// Don't copy the segment map, we will build it again
SetFlags(GetFlags() & ~DynamicObjectFlags::HasSegmentMap);

SetHeadAndLastUsedSegment(dst);
SparseArraySegment<typename T::TElement>* src = SparseArraySegment<typename T::TElement>::From(instance->head);
SparseArraySegment<typename T::TElement>* dst;

if (IsInlineSegment(src, instance))
{
Assert(src->size <= SparseArraySegmentBase::INLINE_CHUNK_SIZE);

// Copy head segment data between inlined head segments
dst = DetermineInlineHeadSegmentPointer<T, 0, true>(static_cast<T*>(this));
dst->left = src->left;
dst->length = src->length;
dst->size = src->size;
}
else
{
// Otherwise, ensure that the new head segment is allocated now in the recycler so that the data can be copied.
// Note: src->next is provided to control whether a leaf segment is allocated just as it is with instance. If
// src->next is non-null, the appropriate update to dst->next will continue below.
dst = SparseArraySegment<typename T::TElement>::AllocateSegment(GetRecycler(), src->left, src->length, src->size, src->next);
}

// Copy head segment data
dst->left = src->left;
dst->length = src->length;
dst->size = src->size;
SetHeadAndLastUsedSegment(dst);
dst->CheckLengthvsSize();

Assert(IsInlineSegment(src, instance) == IsInlineSegment(dst, static_cast<T*>(this)));

CopyArray(dst->elements, dst->size, src->elements, src->size);

if (!deepCopy)
{
// Without a deep copy, point to the existing next segment
// Without a deep copy, point to the existing next segment from the original instance
dst->next = src->next;
}
else
@@ -11845,10 +11863,10 @@ using namespace Js;
{
// Allocate a new segment in the destination and copy from src
// note: PointerValue is to strip SWB wrapping before static_cast
src = static_cast<SparseArraySegment<T>*>(PointerValue(src->next));
src = static_cast<SparseArraySegment<typename T::TElement>*>(PointerValue(src->next));

dst->next = dst->AllocateSegment(GetRecycler(), src->left, src->length, src->size, src->next);
dst = static_cast<SparseArraySegment<T>*>(PointerValue(dst->next));
dst = static_cast<SparseArraySegment<typename T::TElement>*>(PointerValue(dst->next));

CopyArray(dst->elements, dst->size, src->elements, src->size);
}
@@ -11861,14 +11879,20 @@ using namespace Js;
} while (dst != nullptr);
failFastError.Completed();
}

// Assert either
// - there is only the head segment
// - the new head segment points to a new next segment
// - the new head segment points to the existing next segment because this is not a deepCopy
Assert(this->head->next == nullptr || this->head->next != src->next || !deepCopy);
}

JavascriptArray::JavascriptArray(JavascriptArray * instance, bool boxHead, bool deepCopy)
: ArrayObject(instance, deepCopy)
{
if (boxHead)
{
InitBoxedInlineSegments(DetermineInlineHeadSegmentPointer<JavascriptArray, 0, true>(this), SparseArraySegment<Var>::From(instance->head), false);
InitBoxedInlineSegments(instance, deepCopy);
}
else
{
@@ -11880,13 +11904,19 @@ using namespace Js;
}

// Allocate a new Array with its own segments and copy the data in instance
// into the new Array
// into the new Array. If the instance being deepCopy'd has an inline head
// segment, then make sure the new instance also has allocation for an inline
// head segment.
template <typename T>
T * JavascriptArray::DeepCopyInstance(T * instance)
{
return RecyclerNewPlusZ(instance->GetRecycler(),
instance->GetTypeHandler()->GetInlineSlotsSize() + sizeof(Js::SparseArraySegmentBase) + instance->head->size * sizeof(typename T::TElement),
T, instance, true /*boxHead*/, true /*deepCopy*/);
size_t allocSize = instance->GetTypeHandler()->GetInlineSlotsSize();
if (IsInlineSegment(instance->head, instance))
{
allocSize += sizeof(Js::SparseArraySegmentBase) + instance->head->size * sizeof(typename T::TElement);
}

return RecyclerNewPlusZ(instance->GetRecycler(), allocSize, T, instance, true /*boxHead*/, true /*deepCopy*/);
}

ArrayObject* JavascriptArray::DeepCopyInstance(ArrayObject* arrayObject)
@@ -12051,7 +12081,7 @@ using namespace Js;
{
if (boxHead)
{
InitBoxedInlineSegments(DetermineInlineHeadSegmentPointer<JavascriptNativeIntArray, 0, true>(this), SparseArraySegment<int>::From(instance->head), deepCopy);
InitBoxedInlineSegments(instance, deepCopy);
}
else
{
@@ -12097,7 +12127,7 @@ using namespace Js;
{
if (boxHead)
{
InitBoxedInlineSegments(DetermineInlineHeadSegmentPointer<JavascriptNativeFloatArray, 0, true>(this), SparseArraySegment<double>::From(instance->head), deepCopy);
InitBoxedInlineSegments(instance, deepCopy);
}
else
{
@@ -904,7 +904,7 @@ namespace Js
static JavascriptArray * BoxStackInstance(JavascriptArray * instance, bool deepCopy);
static ArrayObject * DeepCopyInstance(ArrayObject * instance);
protected:
template <typename T> void InitBoxedInlineSegments(SparseArraySegment<T> * dst, SparseArraySegment<T> * src, bool deepCopy);
template <typename T> void InitBoxedInlineSegments(T * instance, bool deepCopy);

template <typename T> static T * BoxStackInstance(T * instance, bool deepCopy);
template <typename T> static T * DeepCopyInstance(T * instance);

0 comments on commit dd5b2e7

Please sign in to comment.