CVE-2026-43720
Overview
Background
- Canvas rendering context (m_context)
- A canvas lazily creates and caches one rendering context (2d/webgl/bitmap/webgpu) in m_context on the first getContext call.
- WebIDL dictionary conversion
- Converting a JS argument to a WebIDL dictionary reads its properties, which can invoke author-defined getters and thus run script mid-call.
- Re-entrancy
- Script run during argument conversion can call back into getContext on the same canvas before the outer call finishes.
- ASSERT_WITH_SECURITY_IMPLICATION
- A stronger assertion marking an invariant whose violation is security-relevant, enabled in security test/debug builds.
Root Cause Analysis
HTMLCanvasElement::getContext (and OffscreenCanvas::getContext) convert the JS arguments into a WebIDL settings/attributes dictionary before creating the rendering context, and that dictionary conversion can run author script through getters/valueOf on the supplied object. The pre-patch code checked m_context once at entry, performed the conversion, then unconditionally created a new context and assigned m_context = createContext...(). If script executed during the conversion re-entrantly called getContext() and set m_context, the outer call would overwrite m_context with a second, freshly-created context — dropping the context the inner call had already created (and possibly already returned to script), leaving dangling references to a now-freed context object: a use-after-free / memory corruption.
The fix factors the existing-context path into a getExistingContext lambda and, after every dictionary conversion that can run script, re-checks if (m_context) return getExistingContext(); and guards creation with if (!m_context), so a context established by re-entrancy is honored rather than clobbered. For OffscreenCanvas, the conversion can additionally detach the canvas, so a shouldThrowForDetachedCanvas() re-check is added after each conversion and creation is likewise guarded by if (!m_context). The ASSERT(!m_context) invariants in createContext2d/createContextWebGL/createContextBitmapRenderer/createContextWebGPU were strengthened to ASSERT_WITH_SECURITY_IMPLICATION(!m_context) to catch a double-create.
The restored invariant is that m_context is re-validated after any step that may run script, and a canvas creates exactly one rendering context exactly once.
Attack Path
- Call getContext with a script hook Call canvas.getContext(type, settings) where settings is an object whose getter (or a coercible member) runs attacker script during WebIDL dictionary conversion.
- Re-enter getContext Inside the getter, call canvas.getContext(type, …) again; the inner call creates and assigns m_context and returns a context object to script.
- Clobber the context The outer call resumes after conversion and, pre-patch, creates a second context and overwrites m_context, dropping the inner context.
- Free and dangle The replaced context is destroyed (helped by gc()) while script still references it — use-after-free / memory corruption in WebContent.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
HTMLCanvasElement::getContextSource/WebCore/html/HTMLCanvasElement.cpp |
modified | Extracts a getExistingContext() lambda and, after each 2d/bitmap/webgl dictionary conversion, re-checks `if (m_context) return getExistingContext();` so a re-entrantly-created context is not overwritten. |
HTMLCanvasElement::createContext2d / createContextWebGL / createContextBitmapRenderer / createContextWebGPUSource/WebCore/html/HTMLCanvasElement.cpp |
modified | ASSERT(!m_context) upgraded to ASSERT_WITH_SECURITY_IMPLICATION(!m_context) to catch a double-create as a security-relevant invariant. |
OffscreenCanvas::getContextSource/WebCore/html/OffscreenCanvas.cpp |
modified | Adds shouldThrowForDetachedCanvas() re-checks after conversions (script may detach the canvas) and guards each context creation with if (!m_context) against re-entrant creation. |
Files Changed
LayoutTests/fast/canvas/canvas-getContext-reentrant-expected.txtLayoutTests/fast/canvas/canvas-getContext-reentrant.htmlSource/WebCore/html/HTMLCanvasElement.cppSource/WebCore/html/OffscreenCanvas.cpp
Audit Directions
- Other getContext-style factoriesgrep for a member read, then
releaseReturnValue()/dictionary conversion, then an unconditional member assignment without re-checking the member — the exact clobber shape fixed here. - Assumptions across argument coercionAudit DOM methods that assume a member stays null/unchanged across convert<IDLDictionary>/argument coercion, since coercion can run script and re-enter.
- OffscreenCanvas detach/transferCheck transferToImageBitmap and other OffscreenCanvas paths for state (m_detached/m_context) that can change via script during conversion.