Issue metadata
Sign in to add a comment
|
pwn2own 2016: oob in Array.concat |
||||||||||||||||||||||||
Issue description
Google V8 JavaScript Array Concat Out of Bounds
The Bug
This is a array oob access bug in javascript Array.concat function. A simplified PoC looks like this:
function evil_callback() {
delete Array.prototype[0];
this.length=1; // Free the old storage
return 0.1;
}
Array.prototype.__defineGetter__("0",evil_callback);
var arr=[];
for(var i=1;i<3;i++)
arr[i] = 0.1;
arr = arr.concat();
alert(arr);
In the inner implementation of the concat function, each element of the parameter
array will be visited. If we define a getter function on the “0” property of the Array
prototype, our callback will get invoked when accessing arr[0]. Inside the callback
function, if we set the length of the array to be another value, we are able to free the
storage of the array.
However after returned from our callback function, the old length value will still be used
in concat function, which causes the oob access issue. The rest elements are read from
the alread freed memory and returned in the result array.
3/16/2016 array concat UAF.html
file:///C:/Users/Administrator/Desktop/writeup/cv/array%20concat/array%20concat%20UAF.html 2/7
The source code related to the bug:
/**
* A helper function that visits "array" elements of a JSReceiver in numerical
* order.
*
* The visitor argument called for each existing element in the array
* with the element index and the element's value.
* Afterwards it increments the base‐index of the visitor by the array
* length.
* Returns false if any access threw an exception, otherwise true.
*/
bool IterateElements(Isolate* isolate, Handle<JSReceiver> receiver,
ArrayConcatVisitor* visitor) {
uint32_t length = 0;
if (receiver‐>IsJSArray()) {
Handle<JSArray> array = Handle<JSArray>::cast(receiver);
length = static_cast<uint32_t>(array‐>length()‐>Number());
} else {
Handle<Object> val;
Handle<Object> key = isolate‐>factory()‐>length_string();
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, val, Runtime::GetObjectProperty(isolate, receiver, key),
false);
ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, val,
Object::ToLength(isolate, val), false);
// TODO(caitp): Support larger element indexes (up to 2^53‐1).
if (!val‐>ToUint32(&length)) {
length = 0;
}
}
if (!receiver‐>IsJSArray()) {
// For classes which are not known to be safe to access via elements alone,
// use the slow case.
return IterateElementsSlow(isolate, receiver, length, visitor);
}
3/16/2016 array concat UAF.html
file:///C:/Users/Administrator/Desktop/writeup/cv/array%20concat/array%20concat%20UAF.html 3/7
Handle<JSObject> array = Handle<JSObject>::cast(receiver);
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);
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);
if (!element_value‐>IsTheHole()) {
if (!visitor‐>visit(j, element_value)) return false;
} else {
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,
JSReceiver::GetElement(isolate, array, j), false);‐‐‐‐‐‐‐‐‐‐‐‐‐‐
trigger a callback to js function and modify the length of
if (!visitor‐>visit(j, element_value)) return false;
}
}
}
break;
}
case FAST_HOLEY_DOUBLE_ELEMENTS:
case FAST_DOUBLE_ELEMENTS: {
// Empty array is FixedArray but not FixedDoubleArray.
if (length == 0) break;
// Run through the elements FixedArray and use HasElement and GetElement
// to check the prototype for missing elements.
if (array‐>elements()‐>IsFixedArray()) {
DCHECK(array‐>elements()‐>length() == 0);
generated by haroopad
3/16/2016 array concat UAF.html
file:///C:/Users/Administrator/Desktop/writeup/cv/array%20concat/array%20concat%20UAF.html 4/7
break;
}
Handle<FixedDoubleArray> elements(
FixedDoubleArray::cast(array‐>elements()));
int fast_length = static_cast<int>(length);
DCHECK(fast_length <= elements‐>length());
for (int j = 0; j < fast_length; j++) {
HandleScope loop_scope(isolate);
if (!elements‐>is_the_hole(j)) {
double double_value = elements‐>get_scalar(j);
Handle<Object> element_value =
isolate‐>factory()‐>NewNumber(double_value);
if (!visitor‐>visit(j, element_value)) return false;
} else {
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.
Handle<Object> element_value;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, element_value,
JSReceiver::GetElement(isolate, array, j), false);
if (!visitor‐>visit(j, element_value)) return false;
}
}
}
break;
}
case DICTIONARY_ELEMENTS: {
// CollectElementIndices() can't be called when there's a JSProxy
// on the prototype chain.
for (PrototypeIterator iter(isolate, array); !iter.IsAtEnd();
iter.Advance()) {
if (PrototypeIterator::GetCurrent(iter)‐>IsJSProxy()) {
return IterateElementsSlow(isolate, array, length, visitor);
}
}
Handle<SeededNumberDictionary> dict(array‐>element_dictionary());
3/16/2016 array concat UAF.html
file:///C:/Users/Administrator/Desktop/writeup/cv/array%20concat/array%20concat%20UAF.html 5/7
List<uint32_t> indices(dict‐>Capacity() / 2);
// Collect all indices in the object and the prototypes less
// than length. This might introduce duplicates in the indices list.
CollectElementIndices(array, length, &indices);
indices.Sort(&compareUInt32);
int j = 0;
int n = indices.length();
while (j < n) {
HandleScope loop_scope(isolate);
uint32_t index = indices[j];
Handle<Object> element;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, element, JSReceiver::GetElement(isolate, array, index),
false);
if (!visitor‐>visit(index, element)) return false;
// Skip to next different index (i.e., omit duplicates).
do {
j++;
} while (j < n && indices[j] == index);
}
break;
}
case FAST_SLOPPY_ARGUMENTS_ELEMENTS:
case SLOW_SLOPPY_ARGUMENTS_ELEMENTS: {
for (uint32_t index = 0; index < length; index++) {
HandleScope loop_scope(isolate);
Handle<Object> element;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, element, JSReceiver::GetElement(isolate, array, index),
false);
if (!visitor‐>visit(index, element)) return false;
}
break;
}
case NO_ELEMENTS:
break;
#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype, size) case TYPE##_ELEMENTS:
TYPED_ARRAYS(TYPED_ARRAY_CASE)
#undef TYPED_ARRAY_CASE
case FAST_STRING_WRAPPER_ELEMENTS:
3/16/2016 array concat UAF.html
file:///C:/Users/Administrator/Desktop/writeup/cv/array%20concat/array%20concat%20UAF.html 6/7
case SLOW_STRING_WRAPPER_ELEMENTS:
// |array| is guaranteed to be an array or typed array.
UNREACHABLE();
break;
}
visitor‐>increase_index_offset(length);
return true;
}
Exploit The Bug
To Exploit this bug, we need to trig the bug twice.
For the first time, we trig the bug to leak some memory data, and for the second time,
we will use the leaked data to make a fake ArrayBuffer object to achieve arbitrary
memory read/write.
Stege 1: Memory Leak
In this stage, we trig the bug and in our callback function, after the array storage is
freed, we will try to allocate an ArrayBuffer in the freed array storage memory. So that
by searching the returned array, we can leak the data of the ArrayBuffer. The most
important data we will leak here is the backingstore of the ArrayBuffer.
By leaking the ArrayBuffer’s backingstore, we can have a fixed memory address where
we have full control of its’ content. In this stage, we will also try to leak the address of
a Text object as well as the address of a function object.
Then we will copy the object data of the ArrayBuffer to its’ buffer. It means that now
the backingstore of the ArrayBuffer points to the data which makes up a fake
ArrayObject.
Stage 2: Make Fake ArrayBuffer
Now we need to trig the bug again. This time, we just try to put the fake ArrayBuffer
address (which is the (backingstore_addr | 1)) in the freed array storage memory. So the
concat function will wrongly treat it as an object and returns it in the result array.
Arbitrary Memory R/W
Because the fake ArrayBuffer object is in the backingstore, we have full control on it.
3/16/2016 array concat UAF.html
file:///C:/Users/Administrator/Desktop/writeup/cv/array%20concat/array%20concat%20UAF.html 7/7
Which means we can set the buffer of the fake ArrayObject to any address we want.
This means we are able to read/write arbitrary memory.
Leak the kernel32 module address
In stage 1 we have leaked the addres of a Text object. By reading the Text object’s
data we will be able to leak the base address of chrome_child.dll. Then by searching
chrome_child.dll IAT, we can get the kernel32 module base.
We will pass the leaked kernel32 module address to our flash exploit, to continue the
sandbox escape.
Remote Code Execution
It’s also easy to get RCE in chrome render process after we achieved arbitrary memory
read/write. The easiest way to do so is to modified the JIT code of a JS function to our
shellcode.
,
Mar 16 2016
PoC and original desription
,
Mar 16 2016
looks very similar to issue 594574 maybe?
,
Mar 16 2016
adding the reporter.
,
Mar 16 2016
for the initial pwn2own reporter - patch for issue 594574 https://codereview.chromium.org/1804963002
,
Mar 16 2016
,
Mar 16 2016
,
Mar 17 2016
,
Jun 24 2016
This bug has been closed for more than 14 weeks. Removing security view restrictions. For more details visit https://www.chromium.org/issue-tracking/autotriage - Your friendly Sheriffbot
,
Jun 30 2016
Note: This was ZDI-CAN-3612
,
Jun 20 2018
,
Jun 26 2018
,
Jun 26 2018
|
|||||||||||||||||||||||||
►
Sign in to add a comment |
|||||||||||||||||||||||||
Comment 1 by wfh@chromium.org
, Mar 16 2016