Attacker Value
Moderate
(3 users assessed)
Exploitability
Low
(3 users assessed)
User Interaction
Required
Privileges Required
None
Attack Vector
Network
1

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.

Description

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

3
Ratings
  • Attacker Value
    Medium
  • Exploitability
    Low
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…

2
Technical Analysis

a/third_party/blink/renderer/core/fileapi/file_reader_loader.cc
+++ b/third_party/blink/renderer/core/fileapi/file_reader_loader.cc
@@ -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:

```cpp
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.

Proof-of-Concept

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

https://github.com/exodusintel/CVE-2019-5786

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:
3.6
Exploitability Score:
2.8
Vector:
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H
Attack Vector (AV):
Network
Attack Complexity (AC):
Low
Privileges Required (PR):
None
User Interaction (UI):
Required
Scope (S):
Unchanged
Confidentiality (C):
None
Integrity (I):
None
Availability (A):
High

General Information

Vendors

  • google

Products

  • chrome,
  • puppeteer

Exploited in the Wild

Reported by:
Technical Analysis