POC
function set(arr, key, val) {
arr[key] = val;
}
function leak_hole() {
for(let i = 0; i < 10; i++) {
set(arguments, "foo", 1);
}
set(new Array(), 0, 1);
set(arguments, 0, 1);
return arguments[1];
}
%DebugPrint(leak_hole());
分析
通过对此漏洞的patch分析可知,此漏洞主要是由于没有检查传入的receiver是否是arguments对象并对其做特殊处理导致的:
当在循环执行到第9次时将会触发内联缓调用(前八次都将是no_feedback)AccessorAssembler::KeyedStoreIC(const StoreICParameters* p)
函数,由于第一次触发IC所有会进入miss分支,此分支会进入Runtime_KeyedStoreIC_Miss
函数:
在Runtime_KeyedStoreIC_Miss
函数中会调用KeyedStoreIC::Store
以此来获取Store操作对应的StoreHandler对象,此对象中保存着具体用于从receiver获取属性内容的code:
从KeyedStoreIC::Store
函数开始就将会进入漏洞的主要触发流程,在第九次执行set(arguments, "foo", 1);
时由于key是属性name类型会直接在调用完StoreIC::Store
函数后结束:
在执行set(new Array(), 0, 1);
时会与前一个处理过的arguments对象相同,由于receiver与key都不同所以将会触发miss分支,并调用KeyedStoreIC::Store
函数,与上一行代码不同的是set(new Array(), 0, 1);
key不是一个属性name类型,而是一个元素索引类型,所以KeyedStoreIC::Store
函数会先执行到以下位置:
此处主要用于获取is_arguments
、old_receiver_map
、key_is_valid_inde
、store_mode
几个变量,其中store_mode
比较重要,因为在生成StoreHandle时会根据它来选择具体的存储代码,由于此处处理的是array所以is_arguments
与is_proxy
都将会是false,而key是0也是一个有效的index,所以此处会通过GetStoreMode来获取具体的存储模式:
存储模式主要有四种,根据poc来分析漏洞的话可知此处我们需要STORE_AND_GROW_HANDLE_COW
模式,此模式是一个可扩展的存储模式,由于可扩展模式没有比较严格的边界检查所以会导致之后的问题,想要得到这种模式,要满足四个条件:
- receiver是一个JSArray对象
- 必须得是越界访问
- 当前索引必须小于JSArray最大索引数
- 数组元素必须连续,也就是说必须是PACKED_ELEMENTS类型的数组
之后将会去调用UpdateElements函数,UpdateElements函数会先初始化一个target_maps_and_handlers
列表,通过调试可知这个列表的内容与feedback反馈槽中保存的内容是一至的,通过名称可知这个列表里主要保存map与handler:
map通过执行lambda表达式通过map的过渡树向上遍历map转换关系得到更新后的map,而handler则是通过反馈网络对象获取:
获取完target_maps_and_handlers
后会对单态会用直接用StoreElementHandler
函数去获取StoreHandler,如果不满足单态那就用多态的处理函数StoreElementPolymorphicHandlers
去获取StoreHandler,此处会进入多态处理函数:
StoreElementPolymorphicHandlers
函数会遍历处理target_maps_and_handlers
列表中的每一个map与handler,并且会根据当前map是否具有过渡map来进入不同分支,此处列表中的两个map都不存在过渡,所以会直接去执行StoreElementHandler
函数,StoreElementHandler
函数中会根据store_mode
来获取具体的code,而store_mode
在前面已经提到过是通过当前正在处理的对象,也就是Array对象获取到的STORE_AND_GROW_HANDLE_COW
,所以在遍历中无论是处理Arguments map还是处理Array map都是用STORE_AND_GROW_HANDLE_COW
模式来获取code:
最后得到一个结构如下的feedback,0是arguments对象的map,1为STORE_AND_GROW_HANDLE_COW
模式的code,2为Array对象的map,3同1一样也是STORE_AND_GROW_HANDLE_COW
模式的code:
最后在执行set(arguments, 0, 1)
时由于COW(写入时拷贝)的原因当在具体向arguments对象elelments写入内容时会将elements中的内容完整的拷贝到一块新的elements中并将要写入的内容写入。最后通过arguments[1]
越界读取内容时就会将hole错误的读出来导致hole泄露。
而在官方的patch中的修复方案也比较简单,当在StoreElementHandler
遇到arguments map并且elements是PACKED_ELEMENTS
时,直接不使用外部传入的store_mode,而是直接用标准存储模式STANDARD_STORE
: