3ef3a44d6a1eb85462619684571d8f3cb31999a4 Fix canvas crash with ruby parents
Triage note: Renderer crash (DoS) triggered by simple markup, but the layoutsubtree/drawElementImage feature is experimental and flag-gated, and the diff shows a style-adjustment guard rather than provable corruption.
The bug at a glance
This is a remotely triggerable renderer crash reachable from a tiny, fully static HTML document — no scripting, no user gesture beyond loading the page. That makes it a reliable client-side denial-of-service: any embedded frame (including a cross-origin ad or iframe) can crash the hosting renderer process. It is rated medium rather than high because the observable failure is a layout-time invariant break (a CHECK/DCHECK or immediate null-ish deref inside the canvas layout-subtree code path), not a demonstrated memory-corruption primitive. The canvas ’layoutsubtree’ feature guarantees its children are laid out as an independent block-level subtree; when a child is unexpectedly inlinified the subtree’s block-level precondition is violated and layout aborts. The diff itself shows only the missing guard and a crash-only WPT test (title literally ‘pass if no crash’), with no signal of controllable corruption, so inflating to high would be unsupported. It is not low because the trigger is trivial, deterministic, and cross-origin-reachable. If the underlying crash were later shown to be a use-after-free or OOB rather than a graceful CHECK, the rating would rise.
The feature (part of the drawElement / element-as-image work) establishes an isolated block-level layout subtree under the canvas. StyleAdjuster has a general rule that inlinifies children when the layout parent ‘inlinifies children’ — which ruby (and other inline-forcing contexts) does. Nothing previously exempted canvas-layout-subtree children from that inlinification, so placing a block child under inside turned the child inline, contradicting the invariant that canvas-subtree children are block-level. Downstream layout code that assumes block-level children then hits an unhandled state and crashes. The angle is a missing intersection between two orthogonal styling rules (ruby inlinification vs. canvas subtree block-level requirement) that no test previously exercised together.
Root cause
In blink StyleAdjuster::AdjustStyleForDisplay (style_adjuster.cc), children are inlinified when layout_parent_style.InlinifiesChildren() && !builder.HasOutOfFlowPosition() && ShouldBeInlinified(element). Ruby containers set InlinifiesChildren() so their children are coerced toward inline-level. The canvas layout-subtree feature computes a set of forced styles for canvas children (tracked by force_canvas_child_layout_subtree_styles) that must remain block-level to satisfy the subtree contract. Before the fix, the inlinification branch fired even for canvas-subtree children, converting the child to inline-level. When such a child then entered the canvas layout-subtree layout path, the code encountered an inline where it required a block-level box, breaking the invariant and crashing during layout. The fix adds && !force_canvas_child_layout_subtree_styles to the inlinification condition, so canvas layout-subtree children are never inlinified and stay block-level regardless of an inline-forcing parent like ruby.
Key code
StyleAdjuster inlinification guard: canvas layout-subtree children are now excluded so they remain block-level.
// third_party/blink/renderer/core/css/resolver/style_adjuster.cc
if (layout_parent_style.InlinifiesChildren() &&
!builder.HasOutOfFlowPosition() && ShouldBeInlinified(element) &&
!force_canvas_child_layout_subtree_styles) { // <-- added guard
if (builder.IsFloating()) {
builder.SetFloating(EFloat::kNone);
// ...
}
// ...inlinify child (would violate canvas-subtree block-level invariant)
}
Patch walkthrough
third_party/blink/renderer/core/css/resolver/style_adjuster.cc— Adds the single-condition guard !force_canvas_child_layout_subtree_styles to the block that inlinifies children under an inline-forcing parent. This prevents a canvas layout-subtree child from being demoted to inline-level when its ancestor (e.g. ) requests child inlinification, preserving the block-level invariant the canvas subtree relies on and eliminating the downstream layout crash.third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/ruby-canvas-crash.html— New crash regression test: a wrapping wrapping . It is a ‘pass if no crash’ test — success is simply that layout completes without terminating the renderer. Its location under drawElementImage confirms the layoutsubtree attribute belongs to the element-drawing/drawElement canvas feature.
Background
canvas layoutsubtree — An attribute/feature that makes a ’s DOM children form a real, independently laid-out block-level subtree (used for drawElement/element-as-image rendering, where DOM content is snapshotted into the canvas). The subtree code path assumes block-level children.
InlinifiesChildren() — A ComputedStyle predicate that is true for parents which force their children to inline-level (notably ruby bases/annotations and certain display contexts). When true, StyleAdjuster demotes block children toward inline.
Ruby layout — The element and its layout coerce children into an inline formatting context so base text and annotations align. This inline-forcing behavior is what collides with the canvas subtree’s block-level requirement.
StyleAdjuster — The Blink stage that post-processes ComputedStyle after cascade to enforce structural invariants (e.g. table-internal display fixups, inlinification, out-of-flow handling) before layout tree construction.
Crash-only WPT (reftest-less) — A web platform test whose only assertion is that the browser does not crash while rendering. Common for layout invariant regressions where the pre-fix behavior was a process termination.
Vulnerability window
- introduced — The canvas layout-subtree feature added block-level-child assumptions without exempting those children from generic inlinification, leaving a gap for inline-forcing ancestors.
- reported — Bug 434244126 — canvas with layoutsubtree inside ruby crashes during layout.
- fix — 2026-02-01 (Philip Rogers, r1577867): guard the inlinification path with !force_canvas_child_layout_subtree_styles and add the ruby-canvas-crash WPT regression test.
Proof of concept
The commit itself ships the crash reproducer as a regression test; it is a benign, non-weaponized DOM structure that deterministically triggered the pre-patch renderer crash. It is not exploit code — it demonstrates only a denial-of-service (renderer termination), not memory disclosure or control-flow hijack. Reproduce on an unpatched build by loading the snippet; the tab/renderer crashes during layout.
<!DOCTYPE html>
pass if no crash<br>
<ruby>
<canvas layoutsubtree>
<div></div>
</canvas>
</ruby>
Exploitation
- delivery — Serve or embed the static HTML in any browsing context, including a cross-origin iframe or ad slot on an unpatched build; no JavaScript or user interaction beyond page load is required.
- trigger — During style adjustment the under is inlinified because sets InlinifiesChildren(), violating the canvas subtree’s block-level precondition.
- impact — Layout of the canvas subtree hits the invalid inline-where-block-expected state and terminates the renderer process (client-side DoS). No evidence in the diff of a corruption primitive beyond the crash; treat as availability impact only unless the crash signature proves otherwise.
Detection & hunting
For defenders and detection engineers:
- Renderer crash reports in canvas layout —
- DOM structural pattern —
Audit directions
- Canvas layout-subtree invariants —
- Other inline-forcing ancestors —
- Symmetric subtree features —