CVE-2026-43742

Overview

Advisory: Apple Security Advisory

Impact:

Description: Processing maliciously crafted web content may lead to an unexpected process crash

Researchers: Юлия Мерцалова

Attribute Value
CVE CVE-2026-43742
Bugzilla 315161
Component WebCore
Bug Class UAF
Severity medium
Commit 034f2fbd9b69edba…
Advisory Apple Advisory

Root Cause Analysis

A use-after-free issue was addressed with improved memory management.

Files Changed

Source Files

  • Source/WebCore/html/canvas/WebGL2RenderingContext.h
  • Source/WebCore/html/canvas/WebGLRenderingContext.h
  • Source/WebCore/html/canvas/WebGLRenderingContextBase.cpp
  • Source/WebCore/html/canvas/WebGLRenderingContextBase.h

Test Files

  • LayoutTests/fast/canvas/webgl/context-restore-concurrent-gc-no-crash-expected.txt
  • LayoutTests/fast/canvas/webgl/context-restore-concurrent-gc-no-crash.html

Patch Preview

diff --git a/LayoutTests/fast/canvas/webgl/context-restore-concurrent-gc-no-crash-expected.txt b/LayoutTests/fast/canvas/webgl/context-restore-concurrent-gc-no-crash-expected.txt
new file mode 100644
index 000000000000..c2541f4f3dd7
--- /dev/null
+++ b/LayoutTests/fast/canvas/webgl/context-restore-concurrent-gc-no-crash-expected.txt
@@ -0,0 +1 @@
+PASS if no crash.
diff --git a/LayoutTests/fast/canvas/webgl/context-restore-concurrent-gc-no-crash.html b/LayoutTests/fast/canvas/webgl/context-restore-concurrent-gc-no-crash.html
new file mode 100644
index 000000000000..a62c55af8994
--- /dev/null
+++ b/LayoutTests/fast/canvas/webgl/context-restore-concurrent-gc-no-crash.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html>
+<body>
+<!-- Exercises a race between WebGL2 context restoration (which reinitializes
+     bound-object state via initializeNewContext) and concurrent GC marking (which
+     traverses that state via addMembersToOpaqueRoots). Without objectGraphLock()
+     held during restoration, the restore path can free objects the GC marker is
+     still reading, causing a use-after-free. -->
+<script>
+if (window.testRunner) {
+    testRunner.dumpAsText();
+    testRunner.waitUntilDone();
+}
+if (window.internals)
+    internals.settings.setWebGLErrorsToConsoleEnabled(false);
+
+const tick = () => new Promise(r => setTimeout(r, 0));
+
+// Ensure the GC marker doesn't reach the WebGL wrapper until the concurrent phase.
+const heapPaddingSize = 300000;
+const heapPadding = new Array(heapPaddingSize);
+for (let i = 0; i < heapPaddingSize; i++)
+    heapPadding[i] = { a: i, b: { c: i } };
+
+async function loseAndRestoreContext() {
+    const canvas = document.body.appendChild(document.createElement('canvas'));
+    canvas.width = 1;
+    canvas.height = 1;
+    const gl = canvas.getContext('webgl2');
+    const ext = gl.getExtension('WEBGL_lose_context');
+    for (let i = 0; i < heapPaddingSize; i += 4096)
+        heapPadding[i].g = gl;
+
+    const contextLost = new Promise(r => {
+        canvas.addEventListener('webglcontextlost', e => { e.preventDefault(); r(); });
+    });
+    const contextRestored = new Promise(r => {
+        canvas.addEventListener('webglcontextrestored', () => r());
+    });
+
+    ext.loseContext();
+    await contextLost;
+    await tick();
+
+    // Trigger GC so its concurrent marking phase overlaps the restore timer.
+    new WebAssembly.Memory({ initial: 1024 });
+    new WebAssembly.Memory({ initial: 1024 });
+    ext.restoreContext();
+    await tick();
+    await tick();
+    await contextRestored;
+
+    canvas.remove();
+}
+
+async function runTest() {
+    for (let i = 0; i < 50; i++)
+        await loseAndRestoreContext();
+    document.body.textContent = 'PASS if no crash.';
+    if (window.testRunner)
+        testRunner.notifyDone();
+}
+runTest();
+</script>
+</body>
+</html>
diff --git a/Source/WebCore/html/canvas/WebGL2RenderingContext.h b/Source/WebCore/html/canvas/WebGL2RenderingContext.h
index dcf736d39056..b691cd7a4c8b 100644
--- a/Source/WebCore/html/canvas/WebGL2RenderingContext.h
+++ b/Source/WebCore/html/canvas/WebGL2RenderingContext.h
@@ -261,7 +261,7 @@ class WebGL2RenderingContext final : public WebGLRenderingContextBase {
 
 private:
     using WebGLRenderingContextBase::WebGLRenderingContextBase;
-    void initializeContextState() final;
+    void initializeContextState() WTF_REQUIRES_LOCK(objectGraphLock()) final;
 
     RefPtr<ArrayBufferView> arrayBufferViewSliceFactory(ASCIILiteral functionName, const ArrayBufferView& data, unsigned startByte, unsigned bytelength);
     RefPtr<ArrayBufferView> sliceArrayBufferView(ASCIILiteral functionName, const ArrayBufferView& data, GCGLuint srcOffset, GCGLuint length);
@@ -269,7 +269,7 @@ class WebGL2RenderingContext final : public WebGLRenderingContextBase {
     long long getInt64Parameter(GCGLenum) final;
     Vector<bool> getIndexedBooleanArrayParameter(GCGLenum pname, GCGLuint index);
 
-    void initializeDefaultObjects() final;
+    void initializeDefaultObjects() WTF_REQUIRES_LOCK(objectGraphLock()) final;
     void detachAndRemoveAllObjects() WTF_REQUIRES_LOCK(objectGraphLock()) final;
     bool validateBufferTarget(ASCIILiteral functionName, GCGLenum target) final;
     bool validateBufferTargetCompatibility(ASCIILiteral, GCGLenum, WebGLBuffer*);
diff --git a/Source/WebCore/html/canvas/WebGLRenderingContext.h b/Source/WebCore/html/canvas/WebGLRenderingContext.h
index 7404a87eb9a4..a9b2f1b3e9de 100644
--- a/Source/WebCore/html/canvas/WebGLRenderingContext.h
+++ b/Source/WebCore/html/canvas/WebGLRenderingContext.h
@@ -60,7 +60,7 @@ class WebGLRenderingContext final : public WebGLRenderingContextBase {
 
 private:
     using WebGLRenderingContextBase::WebGLRenderingContextBase;
-    void initializeDefaultObjects() final;
+    void initializeDefaultObjects() WTF_REQUIRES_LOCK(objectGraphLock()) final;
     void detachAndRemoveAllObjects() WTF_REQUIRES_LOCK(objectGraphLock()) final;
 };
 
diff --git a/Source/WebCore/html/canvas/WebGLRenderingContextBase.cpp b/Source/WebCore/html/canvas/WebGLRenderingContextBase.cpp
index f128a2833781..2216b3ef9011 100644
--- a/Source/WebCore/html/canvas/WebGLRenderingContextBase.cpp
+++ b/Source/WebCore/html/canvas/WebGLRenderingContextBase.cpp
@@ -524,8 +524,11 @@ void WebGLRenderingContextBase::initializeNewContext(Ref<GraphicsContextGL> cont
     updateActiveOrdinal();
     if (!wasActive)
         addActiveContext(*this);
-    initializeContextState();
-    initializeDefaultObjects();
+    {
+        Locker locker { objectGraphLock() };
+        initializeContextState();
+        initializeDefaultObjects();
+    }
     // Next calls will receive the context lost callback.
     m_context->setClient(this);
 }
diff --git a/Source/WebCore/html/canvas/WebGLRenderingContextBase.h b/Source/WebCore/html/canvas/WebGLRenderingContextBase.h
index 57fc93560676..9313e4ff0bca 100644
--- a/Source/WebCore/html/canvas/WebGLRenderingContextBase.h
+++ b/Source/WebCore/html/canvas/WebGLRenderingContextBase.h
@@ -538,8 +538,8 @@ class WebGLRenderingContextBase : public GraphicsContextGL::Client, public GPUBa
     friend class ScopedWebGLRestoreTexture;
 
     void initializeNewContext(Ref<GraphicsContextGL>);
-    virtual void initializeContextState();
-    virtual void initializeDefaultObjects();
+    virtual void initializeContextState() WTF_REQUIRES_LOCK(objectGraphLock());
+    virtual void initializeDefaultObjects() WTF_REQUIRES_LOCK(objectGraphLock());
     virtual void detachAndRemoveAllObjects() WTF_REQUIRES_LOCK(objectGraphLock());
 
     // ActiveDOMObject