■ ■ ■ ■ ■ ■
0day-RCAs/2021/CVE-2021-30858.md
| 1 | + | # CVE-2021-30858: Use-after-free in WebKit |
| 2 | + | *Maddie Stone, Google Project Zero* |
| 3 | + | |
| 4 | + | ## The Basics |
| 5 | + | |
| 6 | + | **Disclosure or Patch Date:** 13 September 2021 |
| 7 | + | |
| 8 | + | **Product:** Apple WebKit |
| 9 | + | |
| 10 | + | **Advisory:** https://support.apple.com/en-us/HT212808 |
| 11 | + | |
| 12 | + | **Affected Versions:** pre-Safari 14.1.2, pre-iOS 14.8 |
| 13 | + | |
| 14 | + | **First Patched Version:** Safari 14.1.2, iOS 14.8 |
| 15 | + | |
| 16 | + | **Issue/Bug Report:** https://bugs.webkit.org/show_bug.cgi?id=229535 |
| 17 | + | |
| 18 | + | **Patch CL:** https://github.com/WebKit/WebKit/commit/fbf37d27e313d8d0a150a74cc8fab956eb7f3c59 |
| 19 | + | |
| 20 | + | **Bug-Introducing CL:** https://github.com/WebKit/WebKit/commit/d5dbfd02054e9f904b27224a598ca1bb8ded5f87 |
| 21 | + | |
| 22 | + | **Reporter(s):** Anonymous |
| 23 | + | |
| 24 | + | ## The Code |
| 25 | + | |
| 26 | + | **Proof-of-concept:** |
| 27 | + | |
| 28 | + | ```javascript |
| 29 | + | var fontFace1 = new FontFace("font1", "", {}); |
| 30 | + | var fontFaceSet = new FontFaceSet([fontFace1]); |
| 31 | + | fontFace1.family = "font2"; |
| 32 | + | ``` |
| 33 | + | |
| 34 | + | **Exploit sample:** N/A |
| 35 | + | |
| 36 | + | **Did you have access to the exploit sample when doing the analysis?** No |
| 37 | + | |
| 38 | + | ## The Vulnerability |
| 39 | + | |
| 40 | + | **Bug class:** Use-after-free |
| 41 | + | |
| 42 | + | **Vulnerability details:** |
| 43 | + | The vulnerability is a use-after-free due to an unchecked `end()` iterator. There was an assert statement: `ASSERT(iterator != m_facesLookupTable.end());`, but `ASSERT`s 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. |
| 44 | + | |
| 45 | + | https://github.com/WebKit/WebKit/blob/74bd0da94fa1d31a115bc4ee0e3927d8b2ea571e/Source/WebCore/css/CSSFontFaceSet.cpp#L223 |
| 46 | + | ```c++ |
| 47 | + | void CSSFontFaceSet::removeFromFacesLookupTable(const CSSFontFace& face, const CSSValueList& familiesToSearchFor) |
| 48 | + | { |
| 49 | + | for (auto& item : familiesToSearchFor) { |
| 50 | + | String familyName = CSSFontFaceSet::familyNameFromPrimitive(downcast<CSSPrimitiveValue>(item.get())); |
| 51 | + | if (familyName.isEmpty()) |
| 52 | + | continue; |
| 53 | + | |
| 54 | + | auto iterator = m_facesLookupTable.find(familyName); |
| 55 | + | ASSERT(iterator != m_facesLookupTable.end()); |
| 56 | + | bool found = false; |
| 57 | + | for (size_t i = 0; i < iterator->value.size(); ++i) { |
| 58 | + | if (iterator->value[i].ptr() == &face) { |
| 59 | + | found = true; |
| 60 | + | iterator->value.remove(i); |
| 61 | + | break; |
| 62 | + | } |
| 63 | + | } |
| 64 | + | ASSERT_UNUSED(found, found); |
| 65 | + | if (!iterator->value.size()) |
| 66 | + | m_facesLookupTable.remove(iterator); |
| 67 | + | } |
| 68 | + | } |
| 69 | + | |
| 70 | + | ``` |
| 71 | + | |
| 72 | + | 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. |
| 73 | + | |
| 74 | + | **Patch analysis:** |
| 75 | + | |
| 76 | + | 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. |
| 77 | + | |
| 78 | + | **Thoughts on how this vuln might have been found _(fuzzing, code auditing, variant analysis, etc.)_:** |
| 79 | + | |
| 80 | + | 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. |
| 81 | + | |
| 82 | + | **(Historical/present/future) context of bug:** |
| 83 | + | |
| 84 | + | ## The Exploit |
| 85 | + | |
| 86 | + | (The terms *exploit primitive*, *exploit strategy*, *exploit technique*, and *exploit flow* are [defined here](https://googleprojectzero.blogspot.com/2020/06/a-survey-of-recent-ios-kernel-exploits.html).) |
| 87 | + | |
| 88 | + | **Exploit strategy (or strategies):** |
| 89 | + | |
| 90 | + | N/A no access to exploit sample |
| 91 | + | |
| 92 | + | **Exploit flow:** |
| 93 | + | |
| 94 | + | **Known cases of the same exploit flow:** |
| 95 | + | |
| 96 | + | **Part of an exploit chain?** |
| 97 | + | |
| 98 | + | ## The Next Steps |
| 99 | + | |
| 100 | + | ### Variant analysis |
| 101 | + | |
| 102 | + | **Areas/approach for variant analysis (and why):** |
| 103 | + | |
| 104 | + | * 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. |
| 105 | + | * Fuzz the `FontFace` components. |
| 106 | + | |
| 107 | + | **Found variants:** N/A |
| 108 | + | |
| 109 | + | ### Structural improvements |
| 110 | + | |
| 111 | + | 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.? |
| 112 | + | |
| 113 | + | **Ideas to kill the bug class:** |
| 114 | + | |
| 115 | + | * 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. |
| 116 | + | * Memory-safe languages. This is a memory corruption bug so switching to a memory safe language would also prevent this type of vulnerability. |
| 117 | + | |
| 118 | + | **Ideas to mitigate the exploit flow:** |
| 119 | + | |
| 120 | + | N/A |
| 121 | + | |
| 122 | + | **Other potential improvements:** |
| 123 | + | |
| 124 | + | ### 0-day detection methods |
| 125 | + | |
| 126 | + | 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. |
| 127 | + | |
| 128 | + | ## Other References |
| 129 | + | |