CVE-2024-40782
Overview
Background
- Ruby rendering
- CSS ruby lays out annotation text over base text; WebKit builds dedicated RubyBlock/Ruby render boxes.
- Render-tree continuation
- A block element inside inline content splits the inline into anonymous continuation renderers, changing the expected child structure.
- downcast<>
- An unchecked static cast to a render subtype; casting the wrong renderer type corrupts subsequent operations.
- Anonymous renderer
- An engine-generated wrapper box with no DOM node, inserted to satisfy render-tree invariants.
Root Cause Analysis
RenderTreeBuilder::Ruby::findOrCreateParentForStyleBasedRubyChild assumed that a render box whose display is RubyBlock has, as its direct first child, the RenderElement whose display is Ruby: it did if (parent.style().display()==RubyBlock && parent.firstChild()) { ASSERT(parent.firstChild()->style().display()==Ruby); return downcast<RenderElement>(*parent.firstChild()); }. That invariant holds for a normal ruby subtree, but render-tree continuations break it: a block-level element inside inline ruby content (e.g. a <div> forcing a block-in-inline split, as in the test’s <rb><span>base with <div>forced</div> line break</span>) inserts anonymous continuation wrappers, so the RubyBlock’s first child is an anonymous wrapper, not the Ruby box. Downcasting and returning that wrong renderer as the ruby parent corrupts the render-tree building (operating on a renderer of the wrong type/role), leading to the reported crash.
The fix walks down through anonymous first-children (for (CheckedPtr first = parent.firstChild(); first; first = first->firstChildSlow())), skipping anonymous wrappers and returning the first child whose display is actually Ruby (bailing via ASSERT_NOT_REACHED if a non-anonymous non-ruby child is hit).
The restored invariant is that the ruby parent is located by its actual Ruby display type rather than by blindly taking the first child, so continuations no longer misdirect ruby render-tree construction.
Attack Path
- Craft ruby with a forced continuation Author ruby markup where inline base content contains a block element (e.g. <rb><span>text <div>x</div> more</span></rb>) so the render tree inserts anonymous continuation wrappers under the RubyBlock.
- Force ruby render-tree building Lay out the ruby (e.g. position:absolute) so findOrCreateParentForStyleBasedRubyChild runs on the RubyBlock.
- Mislocate the ruby parent Pre-patch, parent.firstChild() is an anonymous continuation wrapper (not the Ruby box), but it is downcast and returned as the ruby parent.
- Corrupt render-tree construction Subsequent ruby child insertion operates on the wrong renderer, crashing (or corrupting the render tree) in the WebContent process.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
RenderTreeBuilder::Ruby::findOrCreateParentForStyleBasedRubyChildSource/WebCore/rendering/updating/RenderTreeBuilderRuby.cpp |
modified | Walks down anonymous first-children to find the child whose display is actually Ruby, instead of assuming parent.firstChild() is the Ruby box; handles continuation-inserted wrappers. |
Audit Directions
- Other firstChild() type assumptionsgrep RenderTreeBuilder* for firstChild()/lastChild() followed by downcast<> or an ASSERT on display type without walking anonymous wrappers.
- Continuation-affected pathsAudit ruby/table/inline-block builders for structural assumptions broken by block-in-inline continuations.
- ASSERT-guarded downcastsFind ASSERT(x->style().display()==…) immediately before downcast<>; release builds skip the assert and take the bad cast.