前言Javascript中的数组和数组对象一直是程序员优化的主要目标。一般来说,数组只会包含一些基本类型的数据,例如32位整数或字符。因此,每个引擎都会对这些对象进行一定的优化,提高不同元素类型的访问速度和密集表示。在JavaScriptCore中,JavaScript引擎是在WebKit中实现的,对象中存储的每个元素代表一个IndexingType值,一个8位整数代表一组Flag组合。具体的参数定义可以在IndexingType.h中找到。接下来,引擎将检测对象中的索引类型,然后决定使用哪条快速路径。最重要的索引类型是ArrayWithUndecided,这意味着所有元素都是未定义的(undefined),没有实际值。在这种情况下,引擎将保留这些元素未初始化以提高性能。分析下面,我们一起来看一看旧版中现Array.prototype.concat的代码(ArrayPrototype.cpp):EncodedJSValueJSC_HOST_CALLarrayProtoPrivateFuncConcatMemcpy(ExecState*exec){...unsignedresultSize=checkedResultSize-first(););IndexingTypesecondType=secondArray->indexingType();IndexingTypetype=firstArray->mergeIndexingTypeForCopying(secondType);//[[1]]if(type==NonArray||!firstArray->canFastCopy(vm,secondArray)||resultSize>=MIN_SPARSE_ARRAY_INDEX){...}JSGlobalObject*lexicalGlobalObject=exec->lexicalGlobalObject();Structure*resultStructure=lexicalGlobalObject->arrayStructureForIndexingTypeDuringAllocation(type);if(不太可能(hasAnyArrayStorage(resultStructure->indexingType())))returnJSValue::encode(jsNull());ASSERT(!lexicalGlobalObject->isHavingABadTime());ObjectInitializationScopeinitializationScope(vm);JSArray*result=JSArray::tryCreateUninitializedRestricted(initializationScope,resultStructure,resultSize);我f(UNLIKELY(!result)){throwOutOfMemoryError(exec,scope);returnencodedJSValue();}if(type==ArrayWithDouble){[[2]]double*buffer=result->butterfly()->contiguousDouble()。data();memcpy(buffer,firstButterfly->contiguousDouble().data(),sizeof(JSValue)*firstArraySize);memcpy(buffer+firstArraySize,secondButterfly->contiguousDouble().data(),sizeof(JSValue)*secondArraySize);}elseif(type!=ArrayWithUndecided){...该函数主要用于判断结果数组[[1]]的索引类型。我们可以看到,如果索引类型是ArrayWithDouble,它会选择[[2]]作为快速路径接下来我们看一下:mergeIndexingTypeForCopying的实现代码。该函数主要负责在调用Array.prototype.concat时判断结果数组的索引类型:type)||hasAnyArrayStorage(other))returnNonArray;if(type==ArrayWithUndecided)returnother;[[3]]...我们可以看到,在这个例子中,有一个输入数组的索引类型是ArrayWithUndecided,而生成的索引类型将是另一个数组的索引类型。所以如果我们用一个数组索引类型ArrayWithUndecided和另一个数组索引类型ArrayWithDouble调用Array.prototype.concat方法,我们将按照快速路径[[2]]将两个数组组合起来进行拼接。这段代码不保证在代码调用memcpy之前,两只“蝴蝶”(JavaScript引擎攻击技术中的一个概念,详见[本文])能够被正确初始化。这意味着如果我们能找到一个允许我们创建未初始化数组并将其传递给Array.prototype.concat的代码路径,那么我们就可以在堆内存中拥有一个包含未初始化值的数组对象,以及它的索引类型不是ArrayWithUndecided。在某些方面,这个安全问题有点类似于lokihardt在2017年报告的一个旧漏洞,但利用方式不同。创建这样一个数组对象时,可以使用NewArrayWithSizeDFGJIT操作码来实现。在分析FTLLowerDFGToB3.cpp中FTL实现的allocateJSArray操作码后,我们可以看到这个数组将包含未初始化的值。引擎根本不需要初始化数组,因为这个数组的索引类型是ArrayWithUndecided。ArrayValuesallocateJSArray(LValuepublicLength,LValuevectorLength,LValuestructure,LValueindexingType,boolshouldInitializeElements=true,boolshouldLargeArraySizeCreateArrayStorage=true){[...]initializeArrayElements(indexingType,shouldInitializeElements?m_out.int32Zero:publicLength,TypevectorLength,butterflyLValueBeginiting,...voidiniting)LValueend,LValuebutterfly){if(begin==end)return;if(indexingType->hasInt32()){IndexingTyperawIndexingType=static_cast
