Turns out this RCA was describing a different vulnerability, not CVE-2021-30858. Thanks to Apple for letting me know! RCA for the correct bug is forthcoming.

CVE-2021-30858: Use-after-free in WebKit

Maddie Stone, Google Project Zero

The Basics

Disclosure or Patch Date: 13 September 2021

Product: Apple WebKit

Advisory: https://support.apple.com/en-us/HT212808

Affected Versions: pre-Safari 14.1.2, pre-iOS 14.8

First Patched Version: Safari 14.1.2, iOS 14.8

Issue/Bug Report: https://bugs.webkit.org/show_bug.cgi?id=229535

Patch CL: https://github.com/WebKit/WebKit/commit/fbf37d27e313d8d0a150a74cc8fab956eb7f3c59

Bug-Introducing CL: https://github.com/WebKit/WebKit/commit/d5dbfd02054e9f904b27224a598ca1bb8ded5f87

Reporter(s): Anonymous

The Code


var fontFace1 = new FontFace("font1", "", {}); var fontFaceSet = new FontFaceSet([fontFace1]); fontFace1.family = "font2";

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 a use-after-free due to an unchecked end() iterator. There was an assert statement: ASSERT(iterator != m_facesLookupTable.end());, but ASSERTs don't do anything in release builds. Therefore, even if iterator == m_facesLookupTable.end() in the release build, nothing would happen and iterator would still be used.


In FontFaceSet a FontFace is not added to the faces lookup table in addToFacesLookupTable if the font has already been deemed to be invalid. However, removeFromFacesLookupTable would still attempt to remove the font, leading to the use-after-free.

Patch analysis:

The patch changes the ASSERT to an if clause. The function will return if iterator == m_facesLookupTable.end(), since the item it wishes to remove is not found in the table.

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

It seems equally likely that this vulnerability could have been found via fuzzing or code auditing. The trigger is only 3 lines long so it seems like that a fuzzer could have triggered the vulnerability.

(Historical/present/future) context of bug:

The Exploit

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

Exploit strategy (or strategies):

N/A no access to exploit sample

Exploit flow:

Known cases of the same exploit flow:

Part of an exploit chain?

The Next Steps

Variant analysis

Areas/approach for variant analysis (and why):

  • Look for other places where there is an ASSERT checking that the result of find() != end(), but the result can still be used even if that ASSERT fails.
  • Fuzz the FontFace components.

Found variants: N/A

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:

  • Change any ASSERT statements that verifies that a value doesn't equal end() to RELEASE_ASSERT statements. These situations should be caught prior to the RELEASE_ASSERT, but at least if it happens to not be, the RELEASE_ASSERT would prevent exploitation.
  • Memory-safe languages. This is a memory corruption bug so switching to a memory safe language would also prevent this type of vulnerability.

Ideas to mitigate the exploit flow: N/A

Other potential improvements:

0-day detection methods

There doesn't seem to be any really great options for detection due to the trigger for the vulnerability not doing anything that out of the ordinary other than running the samples in a debug WebKit build.

Other References