← All issues

[11] RenderLayer Re-Entrancy During Reflection Layer Teardown

Severity: Medium | Component: WebCore RenderLayerCompositor | ecde527

Rated Medium because the observable effect is access to a partially-destroyed RenderLayer during child teardown (assertion failure via CheckedPtr in debug builds), and while the pre-CheckedPtr behavior in release builds would have accessed invalid memory, the specific memory corruption primitive is uncharacterized — the bug was exposed by a fuzzer hitting the new assertion rather than a demonstrated exploit.

Adds a guard in RenderLayer::removeChild() to skip calling compositor().layerWillBeRemoved() when the child being removed is a reflection layer.

Source/WebCore/rendering/RenderLayer.cpp

void RenderLayer::removeChild(RenderLayer& oldChild)
{
- if (!renderer().renderTreeBeingDestroyed())
+ if (!renderer().renderTreeBeingDestroyed() && !isReflectionLayer(oldChild))
compositor().layerWillBeRemoved(*this, oldChild);
 
// remove the child

LayoutTests/fast/reflections/reflection-removed-crash-2.html

+<style>
+ rt {
+ translate: 23% 80cap 1svi;
+ -webkit-box-reflect: below;
+ }
+</style>
+ <script>
+ (async () => {
+ let n1 = document.createElement('ruby');
+ document.documentElement.appendChild(n1);
+ n1.appendChild(document.createElement('rt'));
+ let n7 = document.createElement('object');
+ n7.id = 'n4';
+ n1.append(document.createElement('rt'));
+ })();
+ testRunner?.dumpAsText();
+ </script>

Re-entrant access to a partially-destroyed object during child teardown in the render layer tree.

RenderLayer is WebKit's representation of a compositing layer in the render tree, forming a tree structure with child layers. A reflection layer is a special child layer created by the -webkit-box-reflect CSS property — it mirrors the visual content of its parent layer. RenderLayerCompositor::layerWillBeRemoved() notifies the compositor that a layer is about to be removed, triggering repaint in the composited ancestor — the nearest ancestor layer that has its own compositing backing store.

When a RenderLayer containing a reflection layer is destroyed, it removes the reflection child first. removeChild() calls compositor().layerWillBeRemoved(*this, oldChild), which calls repaintInCompositedAncestor(). When the composited ancestor of the reflection layer is the parent layer currently being destroyed, the code operates on a partially-destroyed object. A CheckedPtr introduced by commit 306315@main exposed this by asserting that the composited ancestor is valid when the pointer goes out of scope — but the underlying bug existed before: the code was accessing a layer in the process of destruction.

🔒

Explores the object lifecycle implications and whether the partially-destroyed state could be leveraged beyond a crash

Subscribe to read more

🔒

Multiple audit patterns identified around render layer teardown re-entrancy, with concrete starting points in the compositor notification infrastructure

Subscribe to read more