Ben Hawkes, Project Zero

The Basics

Disclosure or Patch Date: 7 April 2020

Product: Google Chrome


Affected Versions: pre 81.0.4044.92

First Patched Version: 81.0.4044.92

Issue/Bug Report:

Patch CL:

Bug-Introducing CL: (Commited on 2016-03-23)

Reporter(s): Anonymous

The Code

Proof-of-concept: N/A

Exploit sample: N/A

Did you have access to the exploit sample when doing the analysis? No

The Vulnerability

Bug class: Use-after-free

Vulnerability details:

The vulnerability is that MediaCodecAudioDecoder::Initialize doesn't reset media_crypto_context_ if media_crypto_ has been previously set. This can occur if MediaCodecAudioDecoder::Initialize is called twice, which is explicitly supported. This is problematic when the second initialization uses a different Content Decryption Module (CDM) than the first one. Each CDM owns the media_crypto_context_ object, and the CDM itself is reference counted. Once the new CDM is set, the old CDM loses a reference and may be destructed. However, MediaCodecAudioDecoder still holds a raw pointer to media_crypto_context_ from the old CDM since it wasn't updated, which results in a UAF on media_crypto_context_ (for example, in MediaCodecAudioDecoder::~MediaCodecAudioDecoder).

In the most abstract sense, the problem arises when one unique_ptr is assigned to another. Assignment makes the original unique_ptr go out of scope, meaning it can be deleted. At the same time a raw pointer from the originally referenced object isn't updated.

Patch analysis:

The patch ensures that double-initialization of the decoder service can't change the CDM. This means that the CDM won't be destructed, and so media_crypto_context_ won't be left dangling.

Thoughts on how this vuln might have been found (fuzzing, code auditing, variant analysis, etc.):

It's most likely that this issue was found through manual review, particularly by studying past issues in this area. It could also have been found statically (e.g. using CodeQL). Fuzzing is possible, but would necessarily have been carried out on a build that supported CDM instantiation (e.g. Widevine support).

(Historical/present/future) context of bug:

  • April 2020: This vulnerability was reported as exploited in-the-wild.
  • September 2019: Man Yue Mo of Semmle reported a very similar vulnerability, CVE-2019-13695.
    • This vulnerability is essentially the same bug as CVE-2020-6572, it's just triggered by an error path after initialize MojoAudioDecoderService twice rather than by re-initializing MediaCodecAudioDecoder.
  • August 2019: Guang Gong of Alpha Team, Qihoo 360 reported a similar vulnerability in the same component, CVE-2019-5870.

The Exploit

(The terms exploit primitive, exploit strategy, exploit technique, and exploit flow are defined here.)

We did not see a copy of the exploit and thus don't know anything about the exploit method.

Exploit strategy (or strategies):

Exploit flow:

Known cases of the same exploit flow:

Part of an exploit chain?

While we don't know for sure, this vulnerability is specifically in the Chrome implementation for Android so it's like that this vulnerability was chained with a Chrome renderer exploit and an Android GPU exploit.

The Next Steps

Variant analysis

Areas/approach for variant analysis (and why):

  • Checked that there are no other instances of cdm_context_ref_ being reassigned. Looks good.
  • Checked related service endpoints for problems with multiple calls to Initialize or similar routines. Most other Initialize routines can only be called once. Decryptor can be called multiple times, but no dangling pointers to be found.
  • Checked for other instances of scoped_refptr's being assigned to in the same area. Nothing else that looks interesting.
  • Generation of cdm_id can wrap (incrementing 32-bit integer). Might lead to problems in MojoCdmService::~MojoCdmService() -- however it's not very practical, this would take 14+ days on a Pixel 3a.

Found variants:

While not a variant, analyzing this vulnerability led to more analysis of Android GPU vulnerabilities. This led to CVE-2020-11179 in Qualcomm's Adreno GPU. blogpost

Structural improvements

What are structural improvements such as ways to kill the bug class, prevent the introduction of this vulnerability, mitigate the exploit flow, make this type of vulnerability harder to exploit, etc.?

Ideas to kill the bug class: Allocator-assisted pointer safety designs like MiraclePtr would likely apply to bugs like this.

Eventually memory-tagging in Chrome's browser/GPU process would potentially make issues like this unexploitable, but hardware isn't generally available as of publication, and important implementation details are still uncertain (for example, regarding synchronous vs asynchronous detection of violations).

Ideas to mitigate the exploit flow: N/A

Other potential improvements:

On desktop systems, Chrome's GPU process and browser process are separate processes that have different sandboxing rules. On Android however (at time of writing in 2020), all of the stuff that's in the GPU process is actually run from a thread in the browser process. Arguably Chrome on Android's design gives you a wider attack surface for the next step in the exploit chain than would be strictly necessary, and increasing process isolation would mean you could further lock down the seccomp-bpf policies.

0-day detection methods

What are potential detection methods for similar 0-days? Meaning are there any ideas of how this exploit or similar exploits could be detected as a 0-day? N/A

Other References

  • February 2021: "Déjà vu-lnerability: A Year in Review of 0-days Exploited In-the-Wild in 2020" blogpost
  • September 2020: "Attacking the Qualcomm Adreno GPU" blogpost
  • August 2020: Guang Gong presented on CVE-2019-5870, a related bug at Blackhat USA 2020, "TiYunZong: An Exploit Chain to Remotely Root Modern Android Devices - Pwn Android Phones from 2015 to 2020" [video, slides, white paper]