Skip to content

Commit 4bdd0ef

Browse files
Thomas Moore (CHAKRA)rajatd
authored andcommitted
[CVE-2018-0993] Edge - Stack-to-heap copy bug in Edge,expose internal property - Qihoo 360
1 parent 396d78f commit 4bdd0ef

File tree

9 files changed

+148
-31
lines changed

9 files changed

+148
-31
lines changed

lib/Runtime/Language/JavascriptOperators.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9499,9 +9499,9 @@ namespace Js
94999499
case Js::TypeIds_Integer:
95009500
return instance;
95019501
case Js::TypeIds_RegEx:
9502-
return JavascriptRegExp::BoxStackInstance(JavascriptRegExp::FromVar(instance));
9502+
return JavascriptRegExp::BoxStackInstance(JavascriptRegExp::FromVar(instance), deepCopy);
95039503
case Js::TypeIds_Object:
9504-
return DynamicObject::BoxStackInstance(DynamicObject::FromVar(instance));
9504+
return DynamicObject::BoxStackInstance(DynamicObject::FromVar(instance), deepCopy);
95059505
case Js::TypeIds_Array:
95069506
return JavascriptArray::BoxStackInstance(JavascriptArray::UnsafeFromVar(instance), deepCopy);
95079507
case Js::TypeIds_NativeIntArray:

lib/Runtime/Library/JavascriptArray.cpp

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11668,7 +11668,7 @@ namespace Js
1166811668
}
1166911669

1167011670
JavascriptArray::JavascriptArray(JavascriptArray * instance, bool boxHead, bool deepCopy)
11671-
: ArrayObject(instance)
11671+
: ArrayObject(instance, deepCopy)
1167211672
{
1167311673
if (boxHead)
1167411674
{
@@ -11683,6 +11683,40 @@ namespace Js
1168311683
}
1168411684
}
1168511685

11686+
// Allocate a new Array with its own segments and copy the data in instance
11687+
// into the new Array
11688+
template <typename T>
11689+
T * JavascriptArray::DeepCopyInstance(T * instance)
11690+
{
11691+
return RecyclerNewPlusZ(instance->GetRecycler(),
11692+
instance->GetTypeHandler()->GetInlineSlotsSize() + sizeof(Js::SparseArraySegmentBase) + instance->head->size * sizeof(typename T::TElement),
11693+
T, instance, true /*boxHead*/, true /*deepCopy*/);
11694+
}
11695+
11696+
ArrayObject* JavascriptArray::DeepCopyInstance(ArrayObject* arrayObject)
11697+
{
11698+
ArrayObject* arrayCopy;
11699+
TypeId typeId = JavascriptOperators::GetTypeId(arrayObject);
11700+
switch (typeId)
11701+
{
11702+
case Js::TypeIds_Array:
11703+
arrayCopy = JavascriptArray::DeepCopyInstance<JavascriptArray>(JavascriptArray::UnsafeFromVar(arrayObject));
11704+
break;
11705+
case Js::TypeIds_NativeIntArray:
11706+
arrayCopy = JavascriptArray::DeepCopyInstance<JavascriptNativeIntArray>(JavascriptNativeIntArray::UnsafeFromVar(arrayObject));
11707+
break;
11708+
case Js::TypeIds_NativeFloatArray:
11709+
arrayCopy = JavascriptArray::DeepCopyInstance<JavascriptNativeFloatArray>(JavascriptNativeFloatArray::UnsafeFromVar(arrayObject));
11710+
break;
11711+
11712+
default:
11713+
AssertAndFailFast(!"Unexpected objectArray type while boxing stack instance");
11714+
arrayCopy = nullptr;
11715+
};
11716+
11717+
return arrayCopy;
11718+
}
11719+
1168611720
template <typename T>
1168711721
T * JavascriptArray::BoxStackInstance(T * instance, bool deepCopy)
1168811722
{
@@ -11713,9 +11747,16 @@ namespace Js
1171311747
// Reallocate both the object as well as the head segment when the head is on the stack or
1171411748
// when a deep copy is needed. This is to prevent a scenario where box may leave either one
1171511749
// on the stack when both must be on the heap.
11716-
boxedInstance = RecyclerNewPlusZ(instance->GetRecycler(),
11717-
inlineSlotsSize + sizeof(Js::SparseArraySegmentBase) + instance->head->size * sizeof(typename T::TElement),
11718-
T, instance, true, deepCopy);
11750+
if (deepCopy)
11751+
{
11752+
boxedInstance = DeepCopyInstance(instance);
11753+
}
11754+
else
11755+
{
11756+
boxedInstance = RecyclerNewPlusZ(instance->GetRecycler(),
11757+
inlineSlotsSize + sizeof(Js::SparseArraySegmentBase) + instance->head->size * sizeof(typename T::TElement),
11758+
T, instance, true /*boxHead*/, false /*deepCopy*/);
11759+
}
1171911760
}
1172011761
else if(inlineSlotsSize)
1172111762
{
@@ -11803,14 +11844,14 @@ namespace Js
1180311844
}
1180411845
#endif
1180511846

11806-
JavascriptNativeArray::JavascriptNativeArray(JavascriptNativeArray * instance) :
11807-
JavascriptArray(instance, false, false),
11847+
JavascriptNativeArray::JavascriptNativeArray(JavascriptNativeArray * instance, bool deepCopy) :
11848+
JavascriptArray(instance, false, deepCopy),
1180811849
weakRefToFuncBody(instance->weakRefToFuncBody)
1180911850
{
1181011851
}
1181111852

1181211853
JavascriptNativeIntArray::JavascriptNativeIntArray(JavascriptNativeIntArray * instance, bool boxHead, bool deepCopy) :
11813-
JavascriptNativeArray(instance)
11854+
JavascriptNativeArray(instance, deepCopy)
1181411855
{
1181511856
if (boxHead)
1181611857
{
@@ -11856,7 +11897,7 @@ namespace Js
1185611897
#endif
1185711898

1185811899
JavascriptNativeFloatArray::JavascriptNativeFloatArray(JavascriptNativeFloatArray * instance, bool boxHead, bool deepCopy) :
11859-
JavascriptNativeArray(instance)
11900+
JavascriptNativeArray(instance, deepCopy)
1186011901
{
1186111902
if (boxHead)
1186211903
{

lib/Runtime/Library/JavascriptArray.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -899,10 +899,12 @@ namespace Js
899899
static uint32 GetSpreadArgLen(Var spreadArg, ScriptContext *scriptContext);
900900

901901
static JavascriptArray * BoxStackInstance(JavascriptArray * instance, bool deepCopy);
902+
static ArrayObject * DeepCopyInstance(ArrayObject * instance);
902903
protected:
903904
template <typename T> void InitBoxedInlineSegments(SparseArraySegment<T> * dst, SparseArraySegment<T> * src, bool deepCopy);
904905

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

907909
public:
908910
template<class T, uint InlinePropertySlots> static size_t DetermineAllocationSize(const uint inlineElementSlots, size_t *const allocationPlusSizeRef = nullptr, uint *const alignedInlineElementSlotsRef = nullptr);
@@ -960,7 +962,7 @@ namespace Js
960962
JavascriptArray(length, type), weakRefToFuncBody(nullptr) {}
961963

962964
// For BoxStackInstance
963-
JavascriptNativeArray(JavascriptNativeArray * instance);
965+
JavascriptNativeArray(JavascriptNativeArray * instance, bool deepCopy);
964966

965967
Field(RecyclerWeakReference<FunctionBody> *) weakRefToFuncBody;
966968

lib/Runtime/Library/JavascriptRegularExpression.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,20 @@ namespace Js
5454
#endif
5555
}
5656

57-
JavascriptRegExp::JavascriptRegExp(JavascriptRegExp * instance) :
58-
DynamicObject(instance),
57+
JavascriptRegExp::JavascriptRegExp(JavascriptRegExp * instance, bool deepCopy) :
58+
DynamicObject(instance, deepCopy),
5959
pattern(instance->GetPattern()),
6060
splitPattern(instance->GetSplitPattern()),
6161
lastIndexVar(instance->lastIndexVar),
6262
lastIndexOrFlag(instance->lastIndexOrFlag)
6363
{
6464
// For boxing stack instance
6565
Assert(ThreadContext::IsOnStack(instance));
66+
67+
// These members should never be on the stack and thus never need to be deep copied
68+
Assert(!ThreadContext::IsOnStack(instance->GetPattern()));
69+
Assert(!ThreadContext::IsOnStack(instance->GetSplitPattern()));
70+
Assert(!ThreadContext::IsOnStack(instance->lastIndexVar));
6671
}
6772

6873
bool JavascriptRegExp::Is(Var aValue)
@@ -1057,7 +1062,7 @@ namespace Js
10571062
DEFINE_FLAG_GETTER(EntryGetterSticky, sticky, IsSticky)
10581063
DEFINE_FLAG_GETTER(EntryGetterUnicode, unicode, IsUnicode)
10591064

1060-
JavascriptRegExp * JavascriptRegExp::BoxStackInstance(JavascriptRegExp * instance)
1065+
JavascriptRegExp * JavascriptRegExp::BoxStackInstance(JavascriptRegExp * instance, bool deepCopy)
10611066
{
10621067
Assert(ThreadContext::IsOnStack(instance));
10631068
// On the stack, the we reserved a pointer before the object as to store the boxed value
@@ -1068,7 +1073,7 @@ namespace Js
10681073
return boxedInstance;
10691074
}
10701075
Assert(instance->GetTypeHandler()->GetInlineSlotsSize() == 0);
1071-
boxedInstance = RecyclerNew(instance->GetRecycler(), JavascriptRegExp, instance);
1076+
boxedInstance = RecyclerNew(instance->GetRecycler(), JavascriptRegExp, instance, deepCopy);
10721077
*boxedInstanceRef = boxedInstance;
10731078
return boxedInstance;
10741079
}

lib/Runtime/Library/JavascriptRegularExpression.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ namespace Js
8282
UnifiedRegex::RegexFlags SetRegexFlag(PropertyId propertyId, UnifiedRegex::RegexFlags flags, UnifiedRegex::RegexFlags flag, ScriptContext* scriptContext);
8383

8484
// For boxing stack instance
85-
JavascriptRegExp(JavascriptRegExp * instance);
85+
JavascriptRegExp(JavascriptRegExp * instance, bool deepCopy);
8686

8787
DEFINE_MARSHAL_OBJECT_TO_SCRIPT_CONTEXT(JavascriptRegExp);
8888
protected:
@@ -201,7 +201,7 @@ namespace Js
201201
virtual uint GetSpecialPropertyCount() const override;
202202
virtual PropertyId const * GetSpecialPropertyIds() const override;
203203

204-
static Js::JavascriptRegExp * BoxStackInstance(Js::JavascriptRegExp * instance);
204+
static Js::JavascriptRegExp * BoxStackInstance(Js::JavascriptRegExp * instance, bool deepCopy);
205205

206206
#if ENABLE_TTD
207207
public:

lib/Runtime/Types/ArrayObject.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010

1111
namespace Js
1212
{
13-
ArrayObject::ArrayObject(ArrayObject * instance)
14-
: DynamicObject(instance),
13+
ArrayObject::ArrayObject(ArrayObject * instance, bool deepCopy)
14+
: DynamicObject(instance, deepCopy),
1515
length(instance->length)
1616
{
1717
}

lib/Runtime/Types/ArrayObject.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ namespace Js
3434
}
3535

3636
// For boxing stack instance
37-
ArrayObject(ArrayObject * instance);
37+
ArrayObject(ArrayObject * instance, bool deepCopy);
3838

3939
void __declspec(noreturn) ThrowItemNotConfigurableError(PropertyId propId = Constants::NoProperty);
4040
void VerifySetItemAttributes(PropertyId propId, PropertyAttributes attributes);

lib/Runtime/Types/DynamicObject.cpp

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ namespace Js
4646
#endif
4747
}
4848

49-
DynamicObject::DynamicObject(DynamicObject * instance) :
49+
DynamicObject::DynamicObject(DynamicObject * instance, bool deepCopy) :
5050
RecyclableObject(instance->type),
5151
auxSlots(instance->auxSlots),
5252
objectArray(instance->objectArray) // copying the array should copy the array flags and array call site index as well
@@ -63,6 +63,8 @@ namespace Js
6363
#if !FLOATVAR
6464
ScriptContext * scriptContext = this->GetScriptContext();
6565
#endif
66+
// Copy the inline slot data from the source instance. Deep copy is implicit because
67+
// the inline slot allocation is already accounted for with the allocation of the object.
6668
for (int i = 0; i < inlineSlotCount; i++)
6769
{
6870
#if !FLOATVAR
@@ -71,11 +73,27 @@ namespace Js
7173
#else
7274
dstSlots[i] = srcSlots[i];
7375
#endif
74-
76+
Assert(!ThreadContext::IsOnStack(dstSlots[i]));
7577
}
7678

7779
if (propertyCount > inlineSlotCapacity)
7880
{
81+
// Properties that are not inlined are stored in the auxSlots, which must be copied
82+
// from the source instance.
83+
84+
// Assert that this block of code will not overwrite inline slot data
85+
Assert(!typeHandler->IsObjectHeaderInlinedTypeHandler());
86+
87+
if (deepCopy)
88+
{
89+
// When a deepCopy is needed, ensure that auxSlots is not shared with the source instance
90+
// so that both objects can have their own, separate lifetimes.
91+
InitSlots(this);
92+
93+
// This auxSlots should now be a separate allocation.
94+
Assert(auxSlots != instance->auxSlots);
95+
}
96+
7997
uint auxSlotCount = propertyCount - inlineSlotCapacity;
8098

8199
for (uint i = 0; i < auxSlotCount; i++)
@@ -84,11 +102,43 @@ namespace Js
84102
// Currently we only support temp numbers assigned to stack objects
85103
auxSlots[i] = JavascriptNumber::BoxStackNumber(instance->auxSlots[i], scriptContext);
86104
#else
105+
// Copy the slot values from that instance to this
106+
Assert(!ThreadContext::IsOnStack(instance->auxSlots[i]));
87107
auxSlots[i] = instance->auxSlots[i];
88108
#endif
109+
Assert(!ThreadContext::IsOnStack(auxSlots[i]));
89110
}
90111
}
91112

113+
if (deepCopy && instance->HasObjectArray())
114+
{
115+
// Assert that this block of code will not overwrite inline slot data
116+
Assert(!typeHandler->IsObjectHeaderInlinedTypeHandler());
117+
118+
// While the objectArray can be any array type, a DynamicObject that is created on the
119+
// stack will only have one of these three types (as these are also the only array types
120+
// that can be allocated on the stack).
121+
Assert(Js::JavascriptArray::Is(instance->GetObjectArrayOrFlagsAsArray())
122+
|| Js::JavascriptNativeIntArray::Is(instance->GetObjectArrayOrFlagsAsArray())
123+
|| Js::JavascriptNativeFloatArray::Is(instance->GetObjectArrayOrFlagsAsArray())
124+
);
125+
126+
// Since a deep copy was requested for this DynamicObject, deep copy the object array as well
127+
SetObjectArray(JavascriptArray::DeepCopyInstance(instance->GetObjectArrayOrFlagsAsArray()));
128+
}
129+
else
130+
{
131+
// Otherwise, assert that there is either
132+
// - no object array to deep copy
133+
// - an object array, but no deep copy needed
134+
// - data in the objectArray member, but it is inline slot data
135+
// - data in the objectArray member, but it is array flags
136+
Assert(
137+
(instance->GetObjectArrayOrFlagsAsArray() == nullptr) ||
138+
(!deepCopy || typeHandler->IsObjectHeaderInlinedTypeHandler() || instance->UsesObjectArrayOrFlagsAsFlags())
139+
);
140+
}
141+
92142
#if ENABLE_OBJECT_SOURCE_TRACKING
93143
TTD::InitializeDiagnosticOriginInformation(this->TTDDiagOriginInfo);
94144
#endif
@@ -805,7 +855,7 @@ namespace Js
805855
}
806856

807857
DynamicObject *
808-
DynamicObject::BoxStackInstance(DynamicObject * instance)
858+
DynamicObject::BoxStackInstance(DynamicObject * instance, bool deepCopy)
809859
{
810860
Assert(ThreadContext::IsOnStack(instance));
811861
// On the stack, the we reserved a pointer before the object as to store the boxed value
@@ -819,11 +869,11 @@ namespace Js
819869
size_t inlineSlotsSize = instance->GetTypeHandler()->GetInlineSlotsSize();
820870
if (inlineSlotsSize)
821871
{
822-
boxedInstance = RecyclerNewPlusZ(instance->GetRecycler(), inlineSlotsSize, DynamicObject, instance);
872+
boxedInstance = RecyclerNewPlusZ(instance->GetRecycler(), inlineSlotsSize, DynamicObject, instance, deepCopy);
823873
}
824874
else
825875
{
826-
boxedInstance = RecyclerNew(instance->GetRecycler(), DynamicObject, instance);
876+
boxedInstance = RecyclerNew(instance->GetRecycler(), DynamicObject, instance, deepCopy);
827877
}
828878

829879
*boxedInstanceRef = boxedInstance;

lib/Runtime/Types/DynamicObject.h

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,21 +84,40 @@ namespace Js
8484
#endif
8585

8686
private:
87+
// Memory layout of DynamicObject can be one of the following:
88+
// (#1) (#2) (#3)
89+
// +--------------+ +--------------+ +--------------+
90+
// | vtable, etc. | | vtable, etc. | | vtable, etc. |
91+
// |--------------| |--------------| |--------------|
92+
// | auxSlots | | auxSlots | | inline slots |
93+
// | union | | union | | |
94+
// +--------------+ |--------------| | |
95+
// | inline slots | | |
96+
// +--------------+ +--------------+
97+
// The allocation size of inline slots is variable and dependent on profile data for the
98+
// object. The offset of the inline slots is managed by DynamicTypeHandler.
99+
// More details for the layout scenarios below.
100+
87101
Field(Field(Var)*) auxSlots;
88-
// The objectArrayOrFlags field can store one of two things:
89-
// a) a pointer to the object array holding numeric properties of this object, or
90-
// b) a bitfield of flags.
102+
103+
// The objectArrayOrFlags field can store one of three things:
104+
// a) a pointer to the object array holding numeric properties of this object (#1, #2), or
105+
// b) a bitfield of flags (#1, #2), or
106+
// c) inline slot data (#3)
91107
// Because object arrays are not commonly used, the storage space can be reused to carry information that
92108
// can improve performance for typical objects. To indicate the bitfield usage we set the least significant bit to 1.
93109
// Object array pointer always trumps the flags, such that when the first numeric property is added to an
94110
// object, its flags will be wiped out. Hence flags can only be used as a form of cache to improve performance.
95111
// For functional correctness, some other fallback mechanism must exist to convey the information contained in flags.
96112
// This fields always starts off initialized to null. Currently, only JavascriptArray overrides it to store flags, the
97113
// bits it uses are DynamicObjectFlags::AllArrayFlags.
114+
// Regarding c) above, inline slots can be stored within the allocation of sizeof(DynamicObject) (#3) or after
115+
// sizeof(DynamicObject) (#2). This is indicated by GetTypeHandler()->IsObjectHeaderInlinedTypeHandler(); when true, the
116+
// inline slots are within the object, and thus the union members *and* auxSlots actually contain inline slot data.
98117

99118
union
100119
{
101-
Field(ArrayObject *) objectArray; // Only if !IsAnyArray
120+
Field(ArrayObject *) objectArray; // Only if !IsAnyArray
102121
struct // Only if IsAnyArray
103122
{
104123
Field(DynamicObjectFlags) arrayFlags;
@@ -122,7 +141,7 @@ namespace Js
122141
DynamicObject(DynamicType * type, ScriptContext * scriptContext);
123142

124143
// For boxing stack instance
125-
DynamicObject(DynamicObject * instance);
144+
DynamicObject(DynamicObject * instance, bool deepCopy);
126145

127146
uint16 GetOffsetOfInlineSlots() const;
128147

@@ -317,7 +336,7 @@ namespace Js
317336
ProfileId GetArrayCallSiteIndex() const;
318337
void SetArrayCallSiteIndex(ProfileId profileId);
319338

320-
static DynamicObject * BoxStackInstance(DynamicObject * instance);
339+
static DynamicObject * BoxStackInstance(DynamicObject * instance, bool deepCopy);
321340

322341
private:
323342
ArrayObject* EnsureObjectArray();

0 commit comments

Comments
 (0)