New issue
Advanced search Search tips

Issue 595485 link

Starred by 2 users

Issue metadata

Status: Duplicate
Merged: issue 594574
Owner: ----
Closed: Mar 2016
Cc:
Components:
EstimatedDays: ----
NextAction: ----
OS: ----
Pri: 1
Type: Bug



Sign in to add a comment

pwn2own 2016: oob in Array.concat

Project Member Reported by wfh@chromium.org, Mar 16 2016

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.

 
array concat oob.pdf
277 KB Download

Comment 1 by wfh@chromium.org, Mar 16 2016

remarked that this is similar to a previously reported bug  issue 554946 

Comment 2 by wfh@chromium.org, Mar 16 2016

PoC and original desription
poc.html
348 bytes View Download
array concat oob.md
9.8 KB View Download
array concat oob.html
45.8 KB View Download

Comment 3 by wfh@chromium.org, Mar 16 2016

looks very similar to  issue 594574  maybe?

Comment 4 by wfh@chromium.org, Mar 16 2016

Cc: higonggu...@gmail.com
adding the reporter.
for the initial pwn2own reporter - patch for  issue 594574  https://codereview.chromium.org/1804963002

Comment 6 Deleted

Comment 7 by wfh@chromium.org, Mar 16 2016

Mergedinto: 594574
Status: Duplicate (was: Untriaged)

Comment 8 by wfh@chromium.org, Mar 16 2016

Cc: higonggu...@gmail.com
Cc: billyleonard@google.com
Project Member

Comment 10 by sheriffbot@chromium.org, Jun 24 2016

Labels: -Restrict-View-SecurityTeam
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
Note: This was ZDI-CAN-3612

Comment 12 by danno@chromium.org, Jun 20 2018

Labels: Hotlist-Torque
Cc: tebbi@chromium.org
Cc: jarin@chromium.org

Sign in to add a comment