1 | | - | # :grimacing: Whoopsie daisy!! :grimacing: |
| 1 | + | ## CVE-2021-30858: WebKit use-after-free in IndexedDB |
| 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 |
2 | 13 | | |
3 | | - | ## 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. |
| 14 | + | **First Patched Version:** Safari 14.1.2, iOS 14.8 |
4 | 15 | | |
| 16 | + | **Issue/Bug Report:** https://bugs.webkit.org/show_bug.cgi?id=229375 |
5 | 17 | | |
6 | | - | ## ~~CVE-2021-30858: Use-after-free in WebKit~~ |
7 | | - | ~~*Maddie Stone, Google Project Zero*~~ |
| 18 | + | **Patch CL:** https://trac.webkit.org/changeset/281384/webkit |
| 19 | + | |
| 20 | + | **Bug-Introducing CL:** ?? |
8 | 21 | | |
9 | | - | ## ~~The Basics~~ |
| 22 | + | **Reporter(s):** Anonymous |
10 | 23 | | |
11 | | - | ~~**Disclosure or Patch Date:** 13 September 2021~~ |
| 24 | + | ## The Code |
12 | 25 | | |
13 | | - | ~~**Product:** Apple WebKit~~ |
| 26 | + | **Proof-of-concept:** |
14 | 27 | | |
15 | | - | ~~**Advisory:** https://support.apple.com/en-us/HT212808~~ |
| 28 | + | index.html |
| 29 | + | ```html |
| 30 | + | <html> |
| 31 | + | <script> |
| 32 | + | w = new Worker('idbworker.js'); |
| 33 | + | </script> |
| 34 | + | </html> |
| 35 | + | ``` |
16 | 36 | | |
17 | | - | ~~**Affected Versions:** pre-Safari 14.1.2, pre-iOS 14.8~~ |
| 37 | + | idbworker.js |
| 38 | + | ```javascript |
| 39 | + | function freememory() { |
| 40 | + | for (var i = 0; i < 1000; i++) { |
| 41 | + | a = new Uint8Array(1024*1024); |
| 42 | + | } |
| 43 | + | } |
18 | 44 | | |
19 | | - | ~~**First Patched Version:** Safari 14.1.2, iOS 14.8~~ |
| 45 | + | let ev = new Event('mine'); |
| 46 | + | let req = indexedDB.open('db'); |
| 47 | + | req.dispatchEvent(ev); |
| 48 | + | req = 0; |
| 49 | + | ev = 0; |
| 50 | + | freememory(); |
| 51 | + | ``` |
20 | 52 | | |
21 | | - | ~~**Issue/Bug Report:** https://bugs.webkit.org/show_bug.cgi?id=229535~~ |
| 53 | + | **Exploit sample:** N/A |
22 | 54 | | |
23 | | - | ~~**Patch CL:** https://github.com/WebKit/WebKit/commit/fbf37d27e313d8d0a150a74cc8fab956eb7f3c59~~ |
| 55 | + | **Did you have access to the exploit sample when doing the analysis?** No |
24 | 56 | | |
25 | | - | ~~**Bug-Introducing CL:** https://github.com/WebKit/WebKit/commit/d5dbfd02054e9f904b27224a598ca1bb8ded5f87~~ |
| 57 | + | ## The Vulnerability |
26 | 58 | | |
27 | | - | ~~**Reporter(s):** Anonymous~~ |
| 59 | + | **Bug class:** Use-after-free |
28 | 60 | | |
29 | | - | ## ~~The Code~~ |
| 61 | + | **Vulnerability details:** |
| 62 | + | There is a use-after-free of the `IDBOpenDBRequest` due to a cross-thread task using a raw reference. `IDBRequest` is the base class of `IDBOpenDBRequest`. The state of the `IDBRequest` is able to be changed in `dispatchEvent` by a script-generated custom event, which leads to the `IDBRequest` being freed too early and thus the use-after-free. |
30 | 63 | | |
31 | | - | ~~**Proof-of-concept:**~~ |
| 64 | + | Prior to this vulnerability being fixed, there were two template options for the `createCrossThreadTask` function: |
| 65 | + | 1. The callee object is a derived class of `ThreadSafeRefCounted<T>` so the cross-thread task will use a `RefPtr` for the callee ([source](https://github.com/WebKit/WebKit/blame/f38367dfe2a83b4cda78235ffd1dc3001743c36e/Source/WTF/wtf/CrossThreadTask.h#L90)): |
32 | 66 | | |
33 | | - | ~~var fontFace1 = new FontFace("font1", "", {}); |
34 | | - | var fontFaceSet = new FontFaceSet([fontFace1]); |
35 | | - | fontFace1.family = "font2";~~ |
| 67 | + | ```c++ |
| 68 | + | template<typename T, typename std::enable_if<std::is_base_of<ThreadSafeRefCounted<T>, T>::value, int>::type = 0, typename... Parameters, typename... Arguments> |
| 69 | + | CrossThreadTask createCrossThreadTask(T& callee, void (T::*method)(Parameters...), const Arguments&... arguments) |
| 70 | + | { |
| 71 | + | return CrossThreadTask([callee = makeRefPtr(&callee), method, arguments = std::make_tuple(crossThreadCopy(arguments)...)]() mutable { |
| 72 | + | callMemberFunctionForCrossThreadTask(callee.get(), method, WTFMove(arguments)); |
| 73 | + | }); |
| 74 | + | } |
| 75 | + | ``` |
36 | 76 | | |
37 | | - | ~~**Exploit sample:** N/A~~ |
| 77 | + | 2. The callee object is NOT a derived class of `ThreadSafeRefCounted<T>` so the cross-thread task will use a raw reference for the callee ([source](https://github.com/WebKit/WebKit/blame/f38367dfe2a83b4cda78235ffd1dc3001743c36e/Source/WTF/wtf/CrossThreadTask.h#L98)): |
38 | 78 | | |
39 | | - | ~~**Did you have access to the exploit sample when doing the analysis?** No~~ |
| 79 | + | ```c++ |
| 80 | + | template<typename T, typename std::enable_if<!std::is_base_of<ThreadSafeRefCounted<T>, T>::value, int>::type = 0, typename... Parameters, typename... Arguments> |
| 81 | + | CrossThreadTask createCrossThreadTask(T& callee, void (T::*method)(Parameters...), const Arguments&... arguments) |
| 82 | + | { |
| 83 | + | return CrossThreadTask([callee = &callee, method, arguments = std::make_tuple(crossThreadCopy(arguments)...)]() mutable { |
| 84 | + | callMemberFunctionForCrossThreadTask(callee, method, WTFMove(arguments)); |
| 85 | + | }); |
| 86 | + | } |
| 87 | + | ``` |
40 | 88 | | |
41 | | - | ## ~~The Vulnerability~~ |
| 89 | + | To trigger this vulnerability, we're using a callee object of `IDBOpenDBRequest`. `IDBOpenDBRequest` is a derived class from `IDBRequest` ([IDBOpenDBRequest.h#L35](https://github.com/WebKit/WebKit/blame/f38367dfe2a83b4cda78235ffd1dc3001743c36e/Source/WebCore/Modules/indexeddb/IDBOpenDBRequest.h#L35)). `IDBRequest` is a derived class from `ThreadSafeRefCounted<IDBRequest>` ([IDBRequest.h#L63](https://github.com/WebKit/WebKit/blame/f38367dfe2a83b4cda78235ffd1dc3001743c36e/Source/WebCore/Modules/indexeddb/IDBRequest.h#L63)). |
42 | 90 | | |
43 | | - | ~~**Bug class:** Use-after-free~~ |
| 91 | + | While the intention was that an `IDBOpenDBRequest` callee would use template #1, the `RefPtr`, #2 was actually used because `IDBOpenDBRequest` is not derived from `ThreadSafeRefCounted<IDBOpenDBRequest>`, it's actually derived from `ThreadSafeRefCounted<IDBRequest>`. Therefore #2 with the raw reference was used. |
44 | 92 | | |
45 | | - | ~~**Vulnerability details:**~~ |
46 | | - | ~~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.~~ |
| 93 | + | **Patch analysis:** |
47 | 94 | | |
48 | | - | ~~https://github.com/WebKit/WebKit/blob/74bd0da94fa1d31a115bc4ee0e3927d8b2ea571e/Source/WebCore/css/CSSFontFaceSet.cpp#L223~~ |
| 95 | + | There are two parts to the patch: |
| 96 | + | 1. In `IDBRequest::dispatchEvent` a check is added to only change the state of the request if the event is trusted, i.e. internally generated. |
| 97 | + | 2. In `CrossThreadTask::createCrossThreadTask` the template is modified to ensure that the callee object, in this case the `IDBOpenDBRequest`, is a `RefPtr` and therefore protected, rather than being a raw reference. This is done by changing the templates to use `ThreadSafeRefCountedBase` instead of `ThreadSafeRefCounted<T>`. |
49 | 98 | | |
50 | | - | ~~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.~~ |
| 99 | + | **Thoughts on how this vuln might have been found _(fuzzing, code auditing, variant analysis, etc.)_:** |
51 | 100 | | |
52 | | - | ~~**Patch analysis:**~~ |
| 101 | + | This bug was most likely found via fuzzing. The trigger for this vulnerability uses all common patterns that would be known to most fuzzers. It is also possible though that the attackers found the vulnerability after seeing the similar Chrome & WebKit bugs. |
53 | 102 | | |
54 | | - | ~~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.~~ |
| 103 | + | **(Historical/present/future) context of bug:** |
55 | 104 | | |
56 | | - | ~~**Thoughts on how this vuln might have been found _(fuzzing, code auditing, variant analysis, etc.)_:**~~ |
| 105 | + | * In December 2019, what seems to be a similar bug was found in Chrome by ClusterFuzz: https://bugs.chromium.org/p/chromium/issues/detail?id=1032890 |
| 106 | + | * In March 2019, what seems to be a similar [bug](https://bugs.webkit.org/show_bug.cgi?id=195563) was found in WebKit. In this case, the use-after-free was on the `IDBDatabase` object. The [patch](https://github.com/WebKit/WebKit/commit/9dc1355f53270b710accba13b54300cca74136c6) included adding the template to `CrossThreadTask::createCrossThreadTask` for callees that are a derived class of `ThreadSafeRefCounted<T>`. |
57 | 107 | | |
58 | | - | ~~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.~~ |
| 108 | + | ## The Exploit |
59 | 109 | | |
60 | | - | ~~**(Historical/present/future) context of bug:**~~ |
| 110 | + | (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).) |
61 | 111 | | |
62 | | - | ## ~~The Exploit~~ |
| 112 | + | **Exploit strategy (or strategies):** |
63 | 113 | | |
64 | | - | ~~(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).)~~ |
| 114 | + | N/A no access to exploit sample |
65 | 115 | | |
66 | | - | ~~**Exploit strategy (or strategies):**~~ |
| 116 | + | **Exploit flow:** |
67 | 117 | | |
68 | | - | ~~N/A no access to exploit sample~~ |
| 118 | + | **Known cases of the same exploit flow:** |
69 | 119 | | |
70 | | - | ~~**Exploit flow:**~~ |
| 120 | + | **Part of an exploit chain?** |
71 | 121 | | |
72 | | - | ~~**Known cases of the same exploit flow:**~~ |
| 122 | + | ## The Next Steps |
73 | 123 | | |
74 | | - | ~~**Part of an exploit chain?**~~ |
| 124 | + | ### Variant analysis |
75 | 125 | | |
76 | | - | ## ~~The Next Steps~~ |
| 126 | + | **Areas/approach for variant analysis (and why):** |
| 127 | + | 1. Check whether other `dispatchEvent` functions change state based on non-trusted (custom/script-generated) events. |
| 128 | + | 2. Check whether tasks that take place in threads other than the origin thread use raw references or pointers. |
| 129 | + | 3. General auditing of IndexedDB code. |
77 | 130 | | |
78 | | - | ### ~~Variant analysis~~ |
| 131 | + | **Found variants:** N/A |
79 | 132 | | |
80 | | - | ~~**Areas/approach for variant analysis (and why):**~~ |
| 133 | + | ### Structural improvements |
81 | 134 | | |
82 | | - | * ~~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.~~ |
83 | | - | * ~~Fuzz the `FontFace` components.~~ |
| 135 | + | 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.? |
84 | 136 | | |
85 | | - | ~~**Found variants:** N/A~~ |
| 137 | + | **Ideas to kill the bug class:** |
86 | 138 | | |
87 | | - | ### ~~Structural improvements~~ |
| 139 | + | * Memory Tagging could potentially prevent this use-after-free. |
88 | 140 | | |
89 | | - | ~~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.?~~ |
| 141 | + | **Ideas to mitigate the exploit flow:** N/A |
90 | 142 | | |
91 | | - | ~~**Ideas to kill the bug class:**~~ |
| 143 | + | **Other potential improvements:** |
92 | 144 | | |
93 | | - | * ~~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.~~ |
94 | | - | * ~~Memory-safe languages. This is a memory corruption bug so switching to a memory safe language would also prevent this type of vulnerability.~~ |
| 145 | + | * Monitoring fixes in other platforms who have similar implementations of code. For example, in [January 2020 Chrome](https://bugs.chromium.org/p/chromium/issues/detail?id=1032890) fixed that [`IDBRequest::DispatchEventInternal` changed state in IndexedDB objects based on untrusted events](https://chromium.googlesource.com/chromium/src.git/+/96f63f777dfc517e46cd41edd9f8205f42055aba%5E%21/#F2). Making that change in WebKit would have fixed this vulnerability. |
95 | 146 | | |
96 | | - | ~~**Ideas to mitigate the exploit flow:** N/A~~ |
| 147 | + | ### 0-day detection methods |
97 | 148 | | |
98 | | - | ~~**Other potential improvements:**~~ |
| 149 | + | These may have high rates of false positives, but here are some ideas for detecting: |
99 | 150 | | |
100 | | - | ### ~~0-day detection methods~~ |
| 151 | + | * Looking for scripts that have functions specifically to trigger garbage collection. |
| 152 | + | * Dispatching custom events to IndexedDB objects. |
101 | 153 | | |
102 | | - | ~~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.~~ |
| 154 | + | ## Other References |
103 | 155 | | |
104 | | - | ## ~~Other References~~ |
105 | 156 | | |