Chakra引擎中JIT编译优化过程中的数组类型混合缝隙分析
颁布功夫 2018-12-18Chakra是一个由微软为Microsoft Edge浏览器开发的JavaScript引擎。它在一个独立的CPU主题上即时编译剧本,与浏览器并行。本文重要对Chakra引擎中JIT编译优化过程中的数组类型混合缝隙进行分析。
JavaScript引擎的机能对整个浏览器的影响至关沉要, JIT编译优化是为了提高Chakra引擎机能。当在循环语句中反复执行统一段剧本代码时,若是诠释器反复执行有关的字节码,效能会很低。JIT能够将源代码直接天活力械指令,鄙人一次执行时直接执行机械指令。在Chakra中只有当指标函数或者循环语句被频仍挪用时才会启用JIT编译,JIT编译后天生了相应的机械指令,下一次挪用到这个语句或是函数时就会直接执行机械指令。
一旦JIT天生实现,法式就能够直接挪用JIT天生的机械指令。由于JIT是直接编译为机械指令的,所以必要预先假定操作指标的类型。若是不满足JIT的如果的话,此JIT代码就不能执行,不然就会产生类型混合的谬误。因而JIT代码中设计了bailout职能,一旦发现不满足如果就进行bailout,bailout会烧毁执行JIT代码转回使用诠释器持续执行字节码。
2、数组类型混合思路
Chakra数组能够分为三类,别离是NativeIntArray、NativeFloatArray和VarArray。NativeIntArray和NativeFloatArray数组转化成VarArray数组过程中会将数组中的原数据通过异或0xfffc000000000000转化为VarArray中的数据。也就是说VarArray会通过数组中元素的高位来判断数组中的元素是数据还是对象。
NativeIntArray和NativeFloatArray之间混合通常不能带来安全问题,但是倒剽二者和VarArray混合之后就会出现数据和对象无法分辨的问题。
先看一段单一代码。
这段代码在JIT优化后的阐发大局是这样的。
若是在xxx操作过程中将NativeArray的类型扭转成了VarArray,并且JIT的优化过程并没有检测到这种变动的话,2.3023e-320就会被当作float数据存放进入VarArray的元素中,由于这个过程中数组的变动是始料未及的,所以2.3023e-320并没有通过与0xfffc000000000000异或而造成一个能够被VarArray识此外float,所以VarArray对象在读取该元素时会将其当成一个对象来处置。
为了实现数组的类型混合,xxx操作主流的思路有两种,一种是通过没有检测的回调来批改数组的类型,第二种是通过合理的函数来批改数组的类型。下面通过一些事俘进行简要分析。
2.1 思路一:通过回调批改数组类型
先来看一个单一的例子,通过回调批改数组类型。
func的JIT重要片段如下:
凭据上述代码,能够看到call rax之后并没有验证数组a是否合法就直接进行了赋值。那么若何扭转数组a的类型呢?我们来看最后一次对func的挪用。
缝隙剧本将一个对象直接赋值给了参数c,并且在这个对象上挂了一个valueOf回调,c要赋值给typed数组b,而b中的元素只能是Uint32类型,所以JIT会对参数c进行一个转换(用到ToInt32),这会触发c的valueOf回调,在回调函数中通过a[0]={}给数组a赋值,这会将a由NativeFloatArray造成VarArray,而后续代码由于没有查抄a数组扭转所以持续将其当作NativeFloatArray赋值造成了类型混合。
补丁后世码如下。
通常来说,Chakra引擎在对JIT中的回调进行优化时会思考一个叫做ImplicitCallFlags的标志位,通过这个标志位,就能够检测用户函数是否可能被挪用,若是是的话就会启动bailout或进行有关检测。但是这种机造存在一些问题,好比ImplicitCallFlags标志位到底在什么地位会被置位,它是否能�;に写嬖诨氐骱牡匚�?
一个典型的例子:CVE-2017-11802
这个缝隙比力单一,存在于RegexHelper::StringReplace函数中,regexp的replace步骤,能够界说一个回调函数,但是在其实现中并没有对回调函数进行�;�,也就是说能够直接在regexp的replace步骤中批改数组类型而不被JIT检测到。
该缝隙的补丁也比力单一,通过对两处挪用回调的地位增长ExecuteImplicitCall验证,就能够建补该缝隙。这个补丁同时建补了一处位于JavascriptArray::ArraySpeciesCreate中的由于创建新对象而导致的回调。
这种机造在实现和优化过程中有没有瑕疵呢?下面来看另一个例子CVE-2018-0840。
这是一个直接对ExecuteImplicitCall函数进行匹敌的缝隙,其问题自身在于ExecuteImplicitCall函数的实现,其代码片段如下。
函数首先会执行implicitCall而后才会更新ImplicitCallFlags,单纯从函数自身来思考如同没什么问题,但是这里面忽略了一个可能就是回调在执行过程中若是出现了一个异常该怎么处置,POC中的typeof实现位于JavascriptOperators::TypeofElem函数中,和缝隙有关的代码如下。
回调会通过ExecuteImplicitCall函数进行挪用,但是回调函数会触发一个异常,该异�;岜籘ypeofElem捕获,也就是说ExecuteImplicitCall函数中更新ImplicitCallFlags的操作被跳过了,由于标志位没有被更新,所以优化过程中的相应排错机造也就没有被天生,最终导致了缝隙的产生。
另表一个问题是CVE-2018-8556,通过补丁信息能够知路缝隙存在于GlobOptBailOut.cpp的MayNeedBailOnImplicitCall函数中,从名字能够揣摩,这个函数重要掌管判断JIT优化过程中是否对ImplicitCall天生bailout代码。
在该函数对对象的length属性进行获取的操作中,判断返回值的逻辑出现了问题。
从逻辑上看,string和满足IsAnyArray并且不蹬宗ObjectWithArray的对象都是能够通过验证的,也就是说typedarray也是满足前提的。
若是要给对象获取length的操作加回调或者过滤操作,对象的length属性的configurable个性必须为true,string和array的length都切合这个假定,但是typedarray却是个例表,所以能够通过给typedarray的length属性加回调的操作,去执行用户界说的代码来触发类型混合缝隙。
2.2 思路二:通过合理的函数挪用批改数组类型
接下来看第二种思路,通过合理函数挪用来触发数组类型扭转。在一些函数处置中,由于职能原因会挪用ToVarArray函数对数组类型进行扭转。
下面举例注明。
opt函数的JIT优化代码如下:
能够看到,在call rax之后并没有进行数组类型的检测就直接赋值了,那么这个call中到底产生了什么呢?这个call挪用了JavascriptOperators::OP_InitProto函数来初始化proto,在最后一次opt挪用时,将array当作proto给了属性链,在对属性链赋值时,若是赋值参数是一个Native数组的话会将其转换为VarArray(挪用了ToVarArray函数)。其挪用函数栈如下。
此时数组的类型已经产生了扭转而JIT并没有查抄到这一点所以产生了缝隙。
再来看一个较为复杂点的例子CVE-2018-0835。
该缝隙存在于JavascriptArray::ReverseHelper函数中,函数会挪用JavascriptArray::FillFromPrototypes,该函数通过遍历prototype来填充array。
在法式中,函数确保prototype中的array不能是NativeArray。
也就是说,若是prototype是NativeArray数组则会被法式转换为VarArray,若是可能使一个数组的prototype为NativeArray,就能够通过数组的Reverse步骤将其prototype的NativeArray转换为VarArray。不外这里还有一个问题就是若何确保prototype是NativeArray,通常情况下若是一个数组被当作prototype,则它会被转化为VarArray。
在JavascriptArray::EntrySort中存在如下代码。
若是arr是一个NativeArray,它首先会造成一个VarArray执行sort回调,再变回NativeArray,若是可能在回调中将这个arr赋给prototype,之后它的类型又会变回来,这样就能够得到一个类型混合缝隙。
2.3 思路三:MissingItem
CVE-2018-0953同样也是通过函数挪用批改数组类型,这个缝隙出格之处在于引出了另一个关注点,即数组的MissingItem。MissingItem是一个数值,在64位法式上蹬宗0x8000000280000002。Chakra引擎在数组创建的时辰会使用这个值对数组元素进行初始化,暗示数组中该元素还未进行赋值,另表数组还会保留一个标志位(NoMissingValues)来标志此数组是否有未被赋值的元素。
先看看下面这段代码。
当执行数组的赋值操作,挪用了NativeArray的SetItem函数,SetItem函数实现如下。
当给NativeArray赋值时,若是这个值蹬宗MissingItem,能够将NativeArray转化为VarArray。优化逻辑如果对数组进行赋值是一个很安全的操作,只有传入参数不是一个对象那么就不会扭转数组类型,但是并没有思考到若是赋值的值蹬宗MissingItem的话会引起数组类型的变动,正是这种忽略导致了缝隙的产生。
这个缝隙自身极度好理解,但是MissingItem自身又引出了一连串的问题。该缝隙的补丁法式建补了通过OP_SetElementI来挪用SetItem的情况,但是这样建补远远不够,由于对该函数挪用的地位其实极度多,因而找缝隙的思路造成了寻找为NativeArray赋值的各类蹊径的问题。
CVE-2018-0953的缝隙发现者lokihardt在补丁建补后又提出两种思路来绕过补丁,第一个是通过arraypush来挪用SetItem。
触发缝隙代码如下:
由于通过push对数组进行插入的操作会挪用SetItem,所以数组扭转的情况仍旧会存在。
第二个思路是先直接批改数组的元素,再通过cancat来批改数组类型。缝隙触发代码如下:
POC首先通过set批改了数组中元素的值。
对应的JIT代码是这样的。
在批改了数组元素后,创造了一个有MissingItem但是HasNoMissingValues的array。
接着剧本挪用了trigger函数,由于数组的HasNoMissingValues标志位为真,下图代码中的前提是满足的。
由于数组有了MissingItem,所以能够进行到如下分支。
InternalFillFromPrototype函数会对buggy数组prototype链上所有对象挪用EnsureNonNativeArray,也就是说会对arr挪用EnsureNonNativeArray,这样就能够批改其数组类型,但是JIT引擎并不知路arr类型已经扭转,所以会导致类型混合。
针对此问题,Chakra的工作人员起头大规模的查抄NativeArray的input,在LowerStElemC、
GenerateProfiledNewScObjArrayFastPath、GenerateHelperToArrayPopFastPath等诸多函数上增长了MissItem的检测(由于建补函数较多,这里就不逐一列举了,详情请参考地址https://github.com/Microsoft/ChakraCore/commit/91bb6d68bfe0455cde08aaa5fbc3f2e4f6cc9d04)。
但是,通过如下代码挪用的OP_Memset函数并没有对value进行查抄,仍旧能够用来机关占有MissingItem但是HasNoMissingValues的array,并通过concat来得到一个类型混合缝隙。
值得一提的是,在11月的补丁中Chakra直接对concat步骤做了严格的处置,从情况上揣摩应该是找到了新的步骤来将MissingItem写入array,但由于网上没找到相应的信息,再加上补丁并没有对将值写入array的代码进行建补,反而限度了concat,所以也无法判断具体情况。
2.4 思路四:将数组假装成对象
最后一种思路,通过蛊惑Chakra引擎,使其在天生JIT代码过程中谬误的将NativeArray当作其他对象,以至于没有在适当的地位增长查抄代码。
公开的例子是CVE-2018-8466。
Chakra使用JavascriptArray::GetArrayForArrayOrObjectWithArray来判断对象是否是array,其逻辑如下所示。
通过CrossSite class来wrap一个对象的时辰会代替该对象的虚表,所以被wrapping的数组将不会被鉴别为数组,这将导致无法在正确的处所天生对数组类型的查抄并产生类型混合缝隙。
补丁除了验证虚表是否是array对象之表,还查抄了对象是否是被CrossSite wrap的数组。
另一个例子是CVE-2018-8542,其补丁在ValueType::MergeWithObject中。
该函数重要用于归并两个对象,能够看到补丁增长了验证,用于确定两个对象中是否罕见组,再观察一下没打过补丁的问题代码,若是两个对象都不是UninitializedObject,则归并为Object对象,大体能够获知缝隙产生的原因,在执行到这句的时辰若是两个对象中有一个是数组,在归并时数组会被当作对象来处置,优化过程中引擎把归并的数组当作了对象,那么对数组类型是否扭转的检测当然就不被必要,因而最终导致了类型混合。
3、总 结
在从前一年左右,JIT编译优化过程中的类型混合是Chakra缝隙挖掘方面的一个重要关注点。从早期的利用未被�;さ幕睾托痴:磁氖槔嘈�,再到寻找验证过程中的逻辑问题,利用数组的MissingItem个性,将数组假装成其他类型对象思路,我们能够看到随着钻研者对Chakra引擎的深刻钻研,缝隙产生的地位已经从单一的对象步骤慢慢向JIT优化代码天生过程中产生的各类逻辑和判断问题挨近,缝隙挖掘的门槛也有了显著的提升。
宝运莱官方网站积极防御尝试室(ADLab)
ADLab成立于1999年,是中国安全行业最早成立的攻防技术钻研尝试室之一,微软MAPP打算主题成员,“黑雀攻击”概想首推者。截止目前,ADLab已通过CVE累计颁布安全缝隙近1000个,通过 CNVD/CNNVD累计颁布安全缝隙近500个,持续维持国际网络安全领域一流水准。尝试室钻研方向涵盖操作系统与利用系统安全钻延注移动智能终端安全钻延注物联网智能设备安全钻延注Web安全钻延注工控系统安全钻延注云安全钻研。钻研成就利用于产品主题技术钻延注国度沉点科技项目攻关、专业安全服务等。


京公网安备11010802024551号