Skip to content

Commit 55358e3

Browse files
author
Suwei Chen
committed
Merge pull request #1982 from suwc:build/suwc/bugfix Type confusion in Array.prototype.filter Type confusion due to reentrancy can cause a Var to be written into a native int array. Fix by making sure type-specialized code path is used only when ArraySpeciesCreate() invokes built-in Array constructor. Heap overflow in Array.prototype.splice In Array.prototype.splice, array length is cached before ArraySpeciesCreate() is invoked. Side-effect from ArraySpeciesCreate() can change array length and result in inconsistent states and possibly heap overflow. Fix by adding length check to keep cases with side effects out of fast path with pre-calculated length. Also tweak logic in ArraySpeciesCreate() to flag a non-built-in constructor with missing [@species] property. Type confusion in FillFromPrototypes In ForEachOwnMissingArrayIndexOfObject(), existing array enumeration logic assumes Var array. A native array from caller can cause type confusion and leak. Fix by converting incoming native arrays to Var arrays. Parameter type confusion in eval Extra argument signified by CallFlags_ExtraArg shall be cast to FrameDisplay unless the extra argument is used for new.target, in which case CallFlags_NewTarget is be set. Type confusion and AV occur because existing logic in eval() does not check if CallFlags_NewTarget is cleared before using extra argument as FrameDisplay. Fix by adding CallFlags_NewTarget check to eval() before cast to FrameDisplay. Type confusion in JSON.parse Non-native array is expected in JSONParser::Walk(). A native array from caller can cause type confusion and heap overflow Fix by converting native arrays to Var arrays. Type confusion in Array.prototype.concat and .splice Array newly created by ArraySpeciesCreate is not being checked if it is a JavascriptCopyOnAccessNativeIntArray, causing near-nullptr AVs. Fix by adding check-and-convert against JavascriptCopyOnAccessNativeIntArray in affected built-ins.
2 parents aee30f0 + 43ea3ab commit 55358e3

File tree

7 files changed

+327
-27
lines changed

7 files changed

+327
-27
lines changed

lib/Runtime/Base/CallInfo.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,16 @@ namespace Js
7272
static const ushort ksizeofCount;
7373
static const ushort ksizeofCallFlags;
7474
static const uint kMaxCountArgs;
75+
76+
static bool isDirectEvalCall(CallFlags flags)
77+
{
78+
// This was recognized as an eval call at compile time. The last one or two args are internal to us.
79+
// Argcount will be one of the following when called from global code
80+
// - eval("...") : argcount 3 : this, evalString, frameDisplay
81+
// - eval.call("..."): argcount 2 : this(which is string) , frameDisplay
82+
83+
return (flags & (CallFlags_ExtraArg | CallFlags_NewTarget)) == CallFlags_ExtraArg; // ExtraArg == 1 && NewTarget == 0
84+
}
7585
};
7686

7787
struct InlineeCallInfo

lib/Runtime/Library/GlobalObject.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,7 @@ namespace Js
493493
// TODO: Handle call from global scope, strict mode
494494
BOOL isIndirect = FALSE;
495495

496-
if (args.Info.Flags & CallFlags_ExtraArg)
496+
if (Js::CallInfo::isDirectEvalCall(args.Info.Flags))
497497
{
498498
// This was recognized as an eval call at compile time. The last one or two args are internal to us.
499499
// Argcount will be one of the following when called from global code

lib/Runtime/Library/JSONParser.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,7 @@ namespace JSON
8383
// this is a post order walk. Visit the children before calling walk on this object
8484
if (Js::DynamicObject::IsAnyArray(value))
8585
{
86-
Js::JavascriptArray* arrayVal = Js::JavascriptArray::FromAnyArray(value);
87-
// REVIEW: How do we guarantee that JSON objects are not native arrays?
86+
Js::JavascriptArray* arrayVal = JavascriptArray::EnsureNonNativeArray(Js::JavascriptArray::FromAnyArray(value));
8887
Assert(!Js::JavascriptNativeIntArray::Is(arrayVal) && !Js::JavascriptNativeFloatArray::Is(arrayVal));
8988
uint length = arrayVal->GetLength();
9089
if (!arrayVal->IsCrossSiteObject())

lib/Runtime/Library/JavascriptArray.cpp

Lines changed: 125 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -752,6 +752,40 @@ namespace Js
752752
return IsMissingHeadSegmentItemImpl<double>(index);
753753
}
754754

755+
template<typename T>
756+
void JavascriptArray::InternalFillFromPrototype(JavascriptArray *dstArray, const T& dstIndex, JavascriptArray *srcArray, uint32 start, uint32 end, uint32 count)
757+
{
758+
RecyclableObject* prototype = srcArray->GetPrototype();
759+
while (start + count != end && JavascriptOperators::GetTypeId(prototype) != TypeIds_Null)
760+
{
761+
ForEachOwnMissingArrayIndexOfObject(srcArray, dstArray, prototype, start, end, dstIndex, [&](uint32 index, Var value) {
762+
T n = dstIndex + (index - start);
763+
dstArray->DirectSetItemAt(n, value);
764+
765+
count++;
766+
});
767+
768+
prototype = prototype->GetPrototype();
769+
}
770+
}
771+
772+
template<>
773+
void JavascriptArray::InternalFillFromPrototype<uint32>(JavascriptArray *dstArray, const uint32& dstIndex, JavascriptArray *srcArray, uint32 start, uint32 end, uint32 count)
774+
{
775+
RecyclableObject* prototype = srcArray->GetPrototype();
776+
while (start + count != end && JavascriptOperators::GetTypeId(prototype) != TypeIds_Null)
777+
{
778+
ForEachOwnMissingArrayIndexOfObject(srcArray, dstArray, prototype, start, end, dstIndex, [&](uint32 index, Var value) {
779+
uint32 n = dstIndex + (index - start);
780+
dstArray->SetItem(n, value, PropertyOperation_None);
781+
782+
count++;
783+
});
784+
785+
prototype = prototype->GetPrototype();
786+
}
787+
}
788+
755789
/* static */
756790
bool JavascriptArray::HasInlineHeadSegment(uint32 length)
757791
{
@@ -3380,6 +3414,9 @@ namespace Js
33803414
pDestObj = ArraySpeciesCreate(args[0], 0, scriptContext);
33813415
if (pDestObj)
33823416
{
3417+
#if ENABLE_COPYONACCESS_ARRAY
3418+
JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(pDestObj);
3419+
#endif
33833420
// Check the thing that species create made. If it's a native array that can't handle the source
33843421
// data, convert it. If it's a more conservative kind of array than the source data, indicate that
33853422
// so that the data will be converted on copy.
@@ -5958,13 +5995,19 @@ namespace Js
59585995
}
59595996

59605997
newArr = CreateNewArrayHelper(static_cast<uint32>(newLenT), isIntArray, isFloatArray, pArr, scriptContext);
5998+
#if ENABLE_COPYONACCESS_ARRAY
5999+
JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(newArr);
6000+
#endif
59616001
newObj = newArr;
59626002
}
59636003
else
59646004
{
59656005
// If the new object we created is an array, remember that as it will save us time setting properties in the object below
59666006
if (JavascriptArray::Is(newObj))
59676007
{
6008+
#if ENABLE_COPYONACCESS_ARRAY
6009+
JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(newObj);
6010+
#endif
59686011
newArr = JavascriptArray::FromVar(newObj);
59696012
}
59706013
}
@@ -6744,6 +6787,9 @@ namespace Js
67446787
// If the new object we created is an array, remember that as it will save us time setting properties in the object below
67456788
if (JavascriptArray::Is(newObj))
67466789
{
6790+
#if ENABLE_COPYONACCESS_ARRAY
6791+
JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(newObj);
6792+
#endif
67476793
newArr = JavascriptArray::FromVar(newObj);
67486794
}
67496795
}
@@ -6752,10 +6798,13 @@ namespace Js
67526798
{
67536799
pArr->GetArrayTypeAndConvert(&isIntArray, &isFloatArray);
67546800
newArr = CreateNewArrayHelper(deleteLen, isIntArray, isFloatArray, pArr, scriptContext);
6801+
#if ENABLE_COPYONACCESS_ARRAY
6802+
JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(newArr);
6803+
#endif
67556804
}
67566805

67576806
// If return object is a JavascriptArray, we can use all the array splice helpers
6758-
if (newArr && isBuiltinArrayCtor)
6807+
if (newArr && isBuiltinArrayCtor && len == pArr->length)
67596808
{
67606809

67616810
// Array has a single segment (need not start at 0) and splice start lies in the range
@@ -7246,6 +7295,9 @@ namespace Js
72467295

72477296
if (JavascriptArray::Is(pNewObj))
72487297
{
7298+
#if ENABLE_COPYONACCESS_ARRAY
7299+
JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(pNewObj);
7300+
#endif
72497301
pnewArr = JavascriptArray::FromVar(pNewObj);
72507302
}
72517303

@@ -8993,6 +9045,9 @@ namespace Js
89939045
// If the new object we created is an array, remember that as it will save us time setting properties in the object below
89949046
if (JavascriptArray::Is(newObj))
89959047
{
9048+
#if ENABLE_COPYONACCESS_ARRAY
9049+
JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(newObj);
9050+
#endif
89969051
newArr = JavascriptArray::FromVar(newObj);
89979052
}
89989053
}
@@ -9182,7 +9237,8 @@ namespace Js
91829237
}
91839238

91849239
// If the source object is an Array exotic object we should try to load the constructor property and use it to construct the return object.
9185-
RecyclableObject* newObj = ArraySpeciesCreate(obj, 0, scriptContext);
9240+
bool isBuiltinArrayCtor = true;
9241+
RecyclableObject* newObj = ArraySpeciesCreate(obj, 0, scriptContext, nullptr, nullptr, &isBuiltinArrayCtor);
91869242
JavascriptArray* newArr = nullptr;
91879243

91889244
if (newObj == nullptr)
@@ -9196,6 +9252,9 @@ namespace Js
91969252
// If the new object we created is an array, remember that as it will save us time setting properties in the object below
91979253
if (JavascriptArray::Is(newObj))
91989254
{
9255+
#if ENABLE_COPYONACCESS_ARRAY
9256+
JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(newObj);
9257+
#endif
91999258
newArr = JavascriptArray::FromVar(newObj);
92009259
}
92019260
}
@@ -9224,7 +9283,7 @@ namespace Js
92249283
if (JavascriptConversion::ToBoolean(selected, scriptContext))
92259284
{
92269285
// Try to fast path if the return object is an array
9227-
if (newArr)
9286+
if (newArr && isBuiltinArrayCtor)
92289287
{
92299288
newArr->DirectSetItemAt(i, element);
92309289
}
@@ -10071,6 +10130,60 @@ namespace Js
1007110130
}
1007210131
#endif
1007310132

10133+
template <typename Fn>
10134+
void JavascriptArray::ForEachOwnArrayIndexOfObject(RecyclableObject* obj, uint32 startIndex, uint32 limitIndex, Fn fn)
10135+
{
10136+
Assert(DynamicObject::IsAnyArray(obj) || JavascriptOperators::IsObject(obj));
10137+
10138+
JavascriptArray* arr = nullptr;
10139+
if (DynamicObject::IsAnyArray(obj))
10140+
{
10141+
arr = JavascriptArray::FromAnyArray(obj);
10142+
}
10143+
else if (DynamicType::Is(obj->GetTypeId()))
10144+
{
10145+
DynamicObject* dynobj = DynamicObject::FromVar(obj);
10146+
arr = dynobj->GetObjectArray();
10147+
}
10148+
10149+
if (arr != nullptr)
10150+
{
10151+
if (JavascriptArray::Is(arr))
10152+
{
10153+
arr = EnsureNonNativeArray(arr);
10154+
ArrayElementEnumerator e(arr, startIndex, limitIndex);
10155+
10156+
while(e.MoveNext<Var>())
10157+
{
10158+
fn(e.GetIndex(), e.GetItem<Var>());
10159+
}
10160+
}
10161+
else
10162+
{
10163+
ScriptContext* scriptContext = obj->GetScriptContext();
10164+
10165+
Assert(ES5Array::Is(arr));
10166+
10167+
ES5Array* es5Array = ES5Array::FromVar(arr);
10168+
ES5ArrayIndexEnumerator<true> e(es5Array);
10169+
10170+
while (e.MoveNext())
10171+
{
10172+
uint32 index = e.GetIndex();
10173+
10174+
if (index < startIndex) continue;
10175+
else if (index >= limitIndex) break;
10176+
10177+
Var value = nullptr;
10178+
if (JavascriptOperators::GetOwnItem(es5Array, index, &value, scriptContext))
10179+
{
10180+
fn(index, value);
10181+
}
10182+
}
10183+
}
10184+
}
10185+
}
10186+
1007410187
template <typename T, typename Fn>
1007510188
void JavascriptArray::ForEachOwnMissingArrayIndexOfObject(JavascriptArray *baseArray, JavascriptArray *destArray, RecyclableObject* obj, uint32 startIndex, uint32 limitIndex, T destIndex, Fn fn)
1007610189
{
@@ -10093,6 +10206,7 @@ namespace Js
1009310206
{
1009410207
if (JavascriptArray::Is(arr))
1009510208
{
10209+
arr = EnsureNonNativeArray(arr);
1009610210
ArrayElementEnumerator e(arr, startIndex, limitIndex);
1009710211

1009810212
while(e.MoveNext<Var>())
@@ -10846,6 +10960,9 @@ namespace Js
1084610960

1084710961
JavascriptArray *JavascriptArray::EnsureNonNativeArray(JavascriptArray *arr)
1084810962
{
10963+
#if ENABLE_COPYONACCESS_ARRAY
10964+
JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(arr);
10965+
#endif
1084910966
if (JavascriptNativeIntArray::Is(arr))
1085010967
{
1085110968
arr = JavascriptNativeIntArray::ToVarArray((JavascriptNativeIntArray*)arr);
@@ -10928,23 +11045,6 @@ namespace Js
1092811045
}
1092911046
}
1093011047

10931-
template<typename T>
10932-
void JavascriptArray::InternalFillFromPrototype(JavascriptArray *dstArray, const T& dstIndex, JavascriptArray *srcArray, uint32 start, uint32 end, uint32 count)
10933-
{
10934-
RecyclableObject* prototype = srcArray->GetPrototype();
10935-
while (start + count != end && JavascriptOperators::GetTypeId(prototype) != TypeIds_Null)
10936-
{
10937-
ForEachOwnMissingArrayIndexOfObject(srcArray, dstArray, prototype, start, end, dstIndex, [&](uint32 index, Var value) {
10938-
T n = dstIndex + (index - start);
10939-
dstArray->DirectSetItemAt(n, value);
10940-
10941-
count++;
10942-
});
10943-
10944-
prototype = prototype->GetPrototype();
10945-
}
10946-
}
10947-
1094811048
Var JavascriptArray::SpreadArrayArgs(Var arrayToSpread, const Js::AuxArray<uint32> *spreadIndices, ScriptContext *scriptContext)
1094911049
{
1095011050
// At this stage we have an array literal with some arguments to be spread.
@@ -11559,6 +11659,11 @@ namespace Js
1155911659
{
1156011660
if (!JavascriptOperators::GetProperty((RecyclableObject*)constructor, PropertyIds::_symbolSpecies, &constructor, scriptContext))
1156111661
{
11662+
if (pIsBuiltinArrayCtor != nullptr)
11663+
{
11664+
*pIsBuiltinArrayCtor = false;
11665+
}
11666+
1156211667
return nullptr;
1156311668
}
1156411669
if (constructor == scriptContext->GetLibrary()->GetNull())

lib/Runtime/Library/JavascriptPromise.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -956,7 +956,7 @@ namespace Js
956956

957957
try
958958
{
959-
values->DirectSetItemAt(index, x);
959+
values->SetItem(index, x, PropertyOperation_None);
960960
}
961961
catch (const JavascriptException& err)
962962
{

0 commit comments

Comments
 (0)