Attacker Value
(3 users assessed)
(3 users assessed)
User Interaction
Privileges Required
Attack Vector

Google Chrome CVE-2019-5786 FileReader Use-After-Free Vulnerability

Disclosure Date: June 27, 2019
Exploited in the Wild
Add MITRE ATT&CK tactics and techniques that apply to this CVE.


Object lifetime issue in Blink in Google Chrome prior to 72.0.3626.121 allowed a remote attacker to potentially perform out of bounds memory access via a crafted HTML page.

Add Assessment

  • Attacker Value
  • Exploitability
Technical Analysis

Exploit doesn’t work within the Chrome sandbox, so an attacker will need to escape that first. Couple that with the facts that x64 is more difficult to target and the auto-update nature of Chrome, this one wouldn’t be the easiest to exploit, methinks…

Technical Analysis

+++ b/third_party/blink/renderer/core/fileapi/
@@ -143,14 +143,16 @@ DOMArrayBuffer* FileReaderLoader::ArrayBufferResult() {
if (!rawdata || errorcode != FileErrorCode::kOK)

 return nullptr;
  • DOMArrayBuffer* result = DOMArrayBuffer::Create(rawdata–>ToArrayBuffer());
  • if (finishedloading) {
  • array_bufferresult = result;
  • AdjustReportedMemoryUsageToV8(
  • -1 * static_cast<int64_t>(rawdata–>ByteLength()));
  • rawdata.reset();
  • if (!finishedloading) {
  • return DOMArrayBuffer::Create(
  • ArrayBuffer::Create(rawdata–>Data(), rawdata–>ByteLength()));
  • return result;
  • array_bufferresult = DOMArrayBuffer::Create(rawdata–>ToArrayBuffer());
  • AdjustReportedMemoryUsageToV8(-1 *
  • static_cast<int64_t>(rawdata–>ByteLength()));
  • rawdata.reset();
  • return array_bufferresult;

String FileReaderLoader::StringResult() {

#### Clue 1: readAsArrayBuffer

This patch provides a lot of insight about the user-after-free. The first thing that stands out is the patched method named `FileReaderLoader::ArrayBufferResult`, which clearly indicates the use of `readAsArrayBuffer` in the exploit.

#### Clue 2: The finished_loading_ condition

A major difference between patched vs unpatched is the `finished_loading_` check, which is a flag that is set when body is finished loading. In the vulnerable version, our method is always doing the following:

DOMArrayBuffer* result = DOMArrayBuffer::Create(raw_data_->ToArrayBuffer());

In the patched version, it is doing:

  if (!finished_loading_) {
    return DOMArrayBuffer::Create(
        ArrayBuffer::Create(raw_data_->Data(), raw_data_->ByteLength()));

  array_buffer_result_ = DOMArrayBuffer::Create(raw_data_->ToArrayBuffer());

The difference is that if loading isn’t finished, it will make sure to create a new ArrayBuffer (which increments the reference counter), instead of obtaining ownership of ArrayBuffer, and passing to MakeGarbageCollected:

static DOMArrayBuffer* Create(scoped_refptr<WTF::ArrayBuffer> buffer) {
  return MakeGarbageCollected<DOMArrayBuffer>(std::move(buffer));

Note: The ToArrayBuffer method always return the actual state of the ArrayBuffer asynchronously.

Clue 3: The Arbitrary free

Another clue comes from the comment in the commit, stating:

… multiple references to the same underlying ArrayBuffer.

This is rather hard to get, but Istvan Kurucsai from Exodus Intel found a great way to acheive that with JavaScript’s postMessage method:

targetWindow.postMessage(message, targetOrigin, [transfer]);

The transfer parameter is a sequence of Transferable objects that are transferred with the message. The ownership of these objects is given to the desitnation side and they are no longer usable on the sending side.

By doing so, we could pass multiple DOMArrayBuffers that refere to the same ArrayBuffer to a JS worker through postMessage in a onprogress event handler, for example:

// last = reader ArrayBuffer result
// lastlast = the previous result
worker.postMessage([last], [last, lastlast]);

This allows the first transfer to take ownership of its buffer, but the second transfer will fail because the ArrayBuffer has already been neutered. When this failure happens, this causes the transferred ArrayBuffer to be freed, while a reference still exists in the second DOMArrayBuffer.


Thanks to Istvan from Exodus, the community gets a clean proof-of-concept for testing purposes:

32-bit Windows 7

One of the reasons memory corruption bugs are much harder to exploit against Chrome is because the use of a custom memory allocator: ParitionAlloc. PartitionAlloc guarantees that different partitions exist in different regions of the process’ address space, it also prevents various scenarios of linear overflows, object allocations, deferences, pointer overwrite, gard pages for large allocations.

It seems one of the limitations with ParitionAlloc is that although it effectively separates our ArrayBuffer from other kinds of allocations, and that it will never reuse those allocations, the scenario only applies if the region that is freed is below 2MiB in size. It is more possible to successfully reclaim the freed region on a 32-bit platform, which explains why a 32-bit Windows 7 is a much more ideal target for Chrome.

CVSS V3 Severity and Metrics
Base Score:
6.5 Medium
Impact Score:
Exploitability Score:
Attack Vector (AV):
Attack Complexity (AC):
Privileges Required (PR):
User Interaction (UI):
Scope (S):
Confidentiality (C):
Integrity (I):
Availability (A):

General Information


  • google


  • chrome

Exploited in the Wild

Reported by:
Technical Analysis