Maddie Stone, Project Zero (Originally posted on Project Zero blog 2020-08-05)

The Basics

Disclosure or Patch Date: 03 April 2020

Product: Mozilla Firefox

Advisory: https://www.mozilla.org/en-US/security/advisories/mfsa2020-11/

Affected Versions: pre-74.0.1

First Patched Version: Firefox 74.0.1 and and Firefox ESR 68.6.1

Issue/Bug Report: https://bugzilla.mozilla.org/show_bug.cgi?id=1626728

Patch CL: https://hg.mozilla.org/mozilla-central/rev/6639deb894172375b05d6791f5f8c7d53ca79723

Bug-Introducing CL: Unknown

Reporter(s): Francisco Alonso @revskills working with Javier Marcos of @JMPSec

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 (UaF)

Vulnerability details:

There is a UaF of the CacheStreamControlParent when closing its last open read stream. The read stream is the response returned to the context process from a cache query. If the close or abort command is received while any read streams are still open, it triggers StreamList::CloseAll. If the StreamControl (must be Parent to get a UAF in browser process, the Child would only provide in renderer) still has ReadStreams when StreamList::CloseAll is called, then this will cause the CacheStreamControlParent to be freed. The mId member of the CacheStreamControl parent is then subsequently accessed, causing the use-after-free. The execution flow is shown below.

StreamList::CloseAll /* PATCHED FUNCTION */
  CacheStreamControlParent::CloseAll
    CacheStreamControlParent::NotifyCloseAll
      StreamControl::CloseAllReadStreams
        For each stream: 
          ReadStream::Inner::CloseStream
            ReadStream::Inner::Close
              ReadStream::Inner::NoteClosed
                …
                StreamControl::NoteClosed
                  StreamControl::ForgetReadStream		
                    CacheStreamControlParent/Child::NoteClosedAfterForget
                      CacheStreamControlParent::RecvNoteClosed
                        StreamList::NoteClosed
                          If StreamList is empty && mStreamControl:
                           CacheStreamControlParent::Shutdown
                             Send__delete(this) /* FREED HERE */
    PCacheStreamControlParent::SendCloseAll /* USED HERE IN CALL TO Id() */

I did not get a working trigger for this vulnerability that didn’t involve shutting down the process. The difficulty is getting there to still be a ReadStream when StreamList::CloseAll is closed. It seems that others, including Firefox engineers, also haven’t found a way to trigger this bug and similar bugs that require winning this race condition that doesn’t involve shut down (here, here, and here). If you have found a way to trigger without shutting down the process, please share.

Patch analysis:

The patch fixes the vulnerability by setting mStreamControl to nullptr prior to calling CacheStreamControlParent::CloseAll. This then prevents the call to CacheStreamControlParent::Shutdown and the freeing of the CacheStreamControlParent.

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

It seems likely that this vulnerability could have been found by:

  1. Manual code auditing/variant analysis after seeing the patch for Bug 1507180 which is a similar UaF in the same module, or
  2. Fuzzing IPDL interfaces. Fuzzing may be slightly more likely due to the difficulty that even Firefox engineers have had in reproducing this bug.

(Historical/present/future) context of bug:

  • CVE-2020-6820 was used as a chain with CVE-2020-6819 (bug report), where 6819 is the renderer exploit and 6820 is the sandbox escape. According to this comment, these two vulnerabilities were found by visiting a URL known to be affiliated with a threat actor, (assumedly) using ASAN Firefox builds.
  • Bug 1507180 is a very similar UaF to this vulnerability. It was patched in December 2019 with an explanatory comment. The bug with ASAN crash wasn’t derestricted until June 2020, but someone may have root caused the bug based on the patches and found this other variant. It’s interesting that engineers for both this vulnerability and the previous bug 1507180 struggled to reproduce the crashes, this likely led to the “race condition” wording of the advisory.

The Exploit

Is the exploit method known? N/A

Exploit method: Unknown

The Next Steps

Variant analysis

Areas/approach for variant analysis (and why): The IPDL interface for the Cache subsystem is very complex. Since there have already been two very similar bugs found in the closing process involving streams, it would be worthwhile to look for other use-after-frees here too.

Found variants:

  • Bug 165115: UAF in StreamControl::CloseReadStreams, but the code is dead code. FF removed it.

Structural improvements

Since the vulnerability was disclosed, Firefox has already begun reducing the use of raw pointers in the Cache module. https://bugzilla.mozilla.org/show_bug.cgi?id=1627892

0-day detection methods

Firefox has crash logs for common crashes here. We could potentially find failed exploitation attempts in these crash logs.

Other References