← WebKit — 2026-W38
e0558372aafca3e5ed5bcc50d0950b60e22e281a Use-after-free in CanvasRenderingContext2DBase::beginLayer via realizeSavesLoop early exit
severity medium
class LogicError
confidence 0.55
signal score 9.5
Source/WebCore/html
Primitive: Canvas2D beginLayer() proceeds to build a filter/layer even when save states were not fully realized, desyncing the save/layer stack.
Triage note: In CanvasRenderingContext2DBase::beginLayer() the fix adds `if (m_unrealizedSaveCount) return;` right after save()/realizeSaves(), so layer setup is skipped when realizeSaves() left pending save states (e.g. a depth cap). Without it the layer stack and the save stack get out of balance, which endLayer()/restore() would later unwind incorrectly — a real state-corruption guard, but the diff alone does not expose a concrete memory-safety primitive.
Triage note: In CanvasRenderingContext2DBase::beginLayer() the fix adds `if (m_unrealizedSaveCount) return;` right after save()/realizeSaves(), so layer setup is skipped when realizeSaves() left pending save states (e.g. a depth cap). Without it the layer stack and the save stack get out of balance, which endLayer()/restore() would later unwind incorrectly — a real state-corruption guard, but the diff alone does not expose a concrete memory-safety primitive.
Structural signals: null_guard (+1.2), msg_strong (+3.0), msg_medium (+2.0), area:Source/WebCore/html (+2.0), surgical (+1.3)
Commit message
Use-after-free in CanvasRenderingContext2DBase::beginLayer via realizeSavesLoop early exit
https://bugs.webkit.org/show_bug.cgi?id=318378
<rdar://175228541>
Reviewed by Said Abou-Hallawa.
beginLayer() relies on realizeSaves() pushing a fresh State onto
m_stateStack so that the new layer's CanvasLayerContextSwitcher is stored
in its own frame. If the state stack is already at MaxSaveCount,
realizeSavesLoop() returns without growing the stack and
m_unrealizedSaveCount remains nonzero. Creating a layer in that case
would overwrite the targetSwitcher of the current top State, dropping the
previous layer while the new layer still holds a raw pointer to the
previous layer's GraphicsContext, leading to a use-after-free.
Fix this by returning early from beginLayer() when realizeSaves() was
unable to push a new State (m_unrealizedSaveCount is still nonzero).
Test: fast/canvas/canvas-layer-after-max-save-count.html
* LayoutTests/fast/canvas/canvas-layer-after-max-save-count-expected.txt: Added.
* LayoutTests/fast/canvas/canvas-layer-after-max-save-count.html: Added.
* Source/WebCore/html/canvas/CanvasRenderingContext2DBase.cpp:
(WebCore::CanvasRenderingContext2DBase::beginLayer):
Originally-landed-as: [email protected] (c5a2b165a95e). rdar://187507031
Canonical link: https://commits.webkit.org/321428@main
Before / after
Loading diff…
On This Page