[1] Unsafe argument capture in EXTDisjointTimerQueryWebGL2::queryCounterEXT()
Severity: High | Component: WebGL — EXTDisjointTimerQueryWebGL2 | 84075f4
Rated High because the observable effect is a use-after-free on a WebGLQuery object reachable from any web page with a WebGL 2 context, and the attacker controls the timing window between lambda creation and GC-triggered destruction — escalation to a controlled UAF primitive depends on heap grooming feasibility (unverified) but the dangling reference path is confirmed by the diff with confidence 0.95.
The lambda passed to queueMicrotask captured the WebGLQuery& query parameter by reference. Since the microtask runs asynchronously, the query object could be destroyed before execution. The fix wraps the capture in a Ref smart pointer.
Source/WebCore/html/canvas/EXTDisjointTimerQueryWebGL2.cpp
- protect(protect(context->scriptExecutionContext())->eventLoop())->queueMicrotask(protect(context->scriptExecutionContext())->vm(), [&] {
- query.makeResultAvailable();
+ protect(protect(context->scriptExecutionContext())->eventLoop())->queueMicrotask(protect(context->scriptExecutionContext())->vm(), [query = Ref { query }] {
+ query->makeResultAvailable();
});
Patch Details
The patch modifies a single call site in EXTDisjointTimerQueryWebGL2::queryCounterEXT(). The lambda's capture list changes from [&] (capture all by reference) to [query = Ref { query }] (capture the query as a reference-counted smart pointer). Inside the lambda body, query.makeResultAvailable() becomes query->makeResultAvailable() to match smart-pointer dereference syntax. No other logic changes — the fix is entirely in the capture semantics.
Dangling reference from by-reference lambda capture of a ref-counted object passed to an asynchronous task.
Background
The EXT_disjoint_timer_query_webgl2 extension provides GPU timestamp queries for WebGL 2 performance measurement. When JavaScript calls queryCounterEXT(), the implementation schedules a microtask to mark the query result as available after the current GPU operation completes.
queueMicrotask schedules a lambda to run asynchronously — after the current JavaScript execution completes but before control returns to the event loop. This creates a temporal gap between when the lambda is created and when it executes.
WebGLQuery is a ref-counted WebGL object representing a GPU query; it can be destroyed when JavaScript drops all references and garbage collection runs. Ref is WebKit's intrusive reference-counting smart pointer — constructing a Ref from a reference increments the refcount and prevents destruction while the Ref is alive. The surrounding code in queryCounterEXT already uses protect() wrappers for the execution context, event loop, and GL objects — but the query parameter itself was captured by plain C++ reference.
Analysis
The root cause is a by-reference capture of a ref-counted object in a lambda that outlives the synchronous scope. Before the fix, queryCounterEXT received WebGLQuery& query as a parameter and captured it via [&] in a lambda passed to queueMicrotask. Since the microtask executes asynchronously, the WebGLQuery object's reference count is not held by the lambda. If JavaScript drops all references to the query object and triggers garbage collection between the queryCounterEXT call and microtask execution, the object is destroyed. When the microtask fires, it calls query.makeResultAvailable() on freed memory — a use-after-free.
The discovery angle is straightforward code review: the [&] capture pattern in a lambda passed to an asynchronous API is a well-known C++ antipattern that can be found through mechanical inspection of capture lists at async scheduling call sites.
Aaa Aaaaaaa Aaaa Aaa Aaaa Aaaaaaaaaaaaaaaaa Aa a Aaaaaaaaaaaaa Aaaaaaaaaaa Aaaa Aaa Aa Aaaaaaaaaa Aa Aaa Aaaaa Aaaaaaa Aaa Aaaaaaa Aa Aaaaaa Aaa Aaaaaaaaa Aaaaaa Aaaa Aaa Aaaaaaaaa Aaaaaaaaa Aa Aaaaa Aaaaaaaaaaaaaaaaaaaaaaa Aa Aaa Aaaaa Aaaaaaaaaaaa Aaa Aaa Aaaaaaaa Aaaaaaaaaa Aa Aaa Aaaaaaaa Aaaaaaaa Aaa Aaaaa Aaaaaa Aaaa a Aaaaaaaaaa Aaaaaaaaaa Aa Aaa Aaaa Aaaaa Aaa Aaaaaa Aaaa Aaaaa Aaaaaaa Aa Aaaaaaaaaaaaaaaaaaa Aaaa a Aa Aaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaa Aaaaaaaa Aaaaa Aa Aaa Aaaaa Aaaaaa Aaa Aaa Aaaaaa Aaaa Aaaaaaaaaa Aaaa Aaaaa Aaaaaaa a Aaaaa Aaaaaaaaa Aa Aaa Aaaaaaaaa Aaaaa
Aaaa Aa Aa Aaaaaaaaaaa Aaa Aaaaaaaaa Aaaa Aaa Aaaaaaa Aaa Aaa Aaaaa a Aaaaa Aaaaa Aaaaaaaaaa Aaa Aaaaaaaa Aaaaaaaa Aaa Aaaaaa Aaaaaa Aaa Aaa Aaaaaaa Aa Aa Aaaa Aaa Aaaaa Aaaaaaa Aaaa Aaaaaaaa Aa Aaaaaaa Aaa Aaaaa Aaaa Aaaaa Aa Aaaaaa Aaa a Aaaaaa Aaaaaaaaa Aaaaaa a Aaaaaa
Aaaa Aaaaaaaaaaaaa Aaaaaaa Aaaaaa Aaaaaa Aa Aaa Aaaaaaaaaa Aaaaaaaa Aaaaaaaa Aaa Aaaaa Aaaaa Aaaaa Aaa Aa Aaaaaaaaa Aaaa Aaa Aaa Aaaa Aaaa Aaaaaaa a Aaaaa a Aaaaaaa Aaa Aaaaaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaa Aa Aaaaaaaa Aaa Aaaaaaaa Aaaaaaaaaaa Aa a Aaaaaaaaaaaa Aaaaaa Aaaaaaa Aaa Aaaaaaaaaaaaaaaaa Aaaa Aaa Aaaaaaaaa Aaaaaaaaa Aaaaa Aaaa Aaa Aa Aaaaa Aaaaaa Aaaaaaa Aaa Aaaaaaaa Aaaaaaaaaa Aaaaaaaaaaa Aaaaaaaaa Aaaa Aaaaaaaaa Aaaaaa Aaa Aaaaaaaa Aaaaaaaa a Aaaaaaaa Aaaaaaa Aaaaaa Aaaaa Aa Aaaaaa Aaa Aaaa Aaaaaa Aaaaaaaaaaa
Aaaa Aa a Aaaaaaaa Aaaaaaaaaaaa Aaaaaaa Aaaa Aaa Aaaaaaaaaaa Aaaaaaa Aa Aaaa Aaaaaaaaaaa Aa Aaa Aaaaaaaaaaa Aaaa Aaa Aaa Aaaaaaaa Aaaaa Aaaaa Aaa Aa Aaaaaaa a Aaa Aaa Aaa Aaaaaaa Aa Aaa Aaaaa Aaaaaaaaa Aaaaaaa Aaaaaaaaaa Aaa Aaaaaa Aaa Aaaa Aaaaaaaa Aaaaaaa Aaa Aaaaa Aa Aaaaaa Aaa Aaaa Aaaaaaaa Aa Aaa Aaaaaaaaaaa Aaaaa
Aaaaaaaaa Aaaa Aaaaaaaaaaaaaa Aaaaaaa a Aaaaaaaaaaaa Aaaaaaa Aaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaa Aaaaaaaa Aaaaa Aa a Aaa Aaaa Aaaaaa a Aaaaa Aaaaaaaaaa Aaa Aaaaaaa Aaa Aaaaa a Aaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaa Aaa Aaaa Aaaaaaa a Aaa Aaaaaaaa Aaaa Aaa Aaaaaa Aaaa Aaa Aaaaaaaa Aaa Aaaaaaaaa Aaaaaa Aaaa Aaaaaaaa Aaaaaaa Aa Aaa Aaaaa Aaa Aaaa Aaa Aaaaaaaaa Aaa Aaa Aaa Aaaaaaaaaaaa Aaaaaaaaa Aa Aaa Aaaaaa
🔒Explores the exploitation window and heap reclamation feasibility for this asynchronous lifetime bug
Subscribe to read more
Audit directions
a Aaaaaaaaaa Aaaaaaaaaaaa Aaaaaa Aaaaaaa Aa Aaaaaaaaaaa Aaaaaaa Aaaaaa Aa Aaaaaaaaaaaa Aaaaaaaaaa Aaaaaa Aaaaaaaaaaaaaaaaaa Aaaaaaaaaaaa Aaaaaaaaaaa Aaaaa Aaaaaaaaaaa Aaa Aaaa Aaaaaaa Aaaaaaa a Aaa Aaaaaa Aa Aaa Aaaaaa Aaa Aa Aaaa Aaaaaa Aaa Aaaa Aaaaaa Aaaa Aaa Aaaaa Aa Aaaa Aaaaaaa Aaaaa Aa Aaaaaaa Aaaaaa Aa Aaaaaaaaaaaaaaaaa Aaaaaaaaaaaa Aaaaaaaaaaaaaaa Aaa Aaaaaaaaaa Aaaaaa Aaaaaaaaaaaaaaa Aaaaa Aaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaa Aaa a Aaaaaaaa Aaaaaaaaaaaaaa Aaa Aaaaa a Aaaa Aaa Aaaaa Aaa Aaaa Aaaaaaaa
a Aaaaaaaaaa Aaaaa Aaaaaaaaa Aaaaaaa Aaaa Aaaaaaaa Aaaaaaaa Aaaa Aa Aaaaa Aa Aaaaaa Aaaaaaaaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaa Aaa Aaaaa a Aaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa a Aaaaaaa Aaaaaaaaaaaaaaaaa Aaaaaa a Aaaaaa Aaa Aaaaaa Aaaaaaaa Aaa Aaaaa Aaaaaa Aaaa Aaaaaaaaaaaaa Aaaa Aaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaa Aaaaaaaa Aaaaaaaaaa Aa Aaaaa Aaaaaaa Aaaaaa Aa Aaaaaaaaaa
a Aaaaaaa Aaaaa Aaaaaaaaaaaaaaaa Aaaa Aaaaa Aaaaaa Aaaaaaaaa Aaa Aaa Aaaa Aaaaaaa Aaaaa Aa Aaa Aaaaaaa Aa Aaaaaaaaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aa Aaaaaaaaaa Aaaaaaaaaaaaa Aa Aaaaa Aaaaaaaaaaaa Aaaaaaa Aa Aaaaa Aaaa Aaaaa Aa a Aaaaaaaaa Aaaa Aaaa Aaaaaaaaaaaaaaaa Aa Aaaaaaaaaaaaaa Aaa Aaaaaaa Aaaa Aaaaaaaa Aaaaaaa Aaaaa
🔒Multiple audit patterns identified for similar async capture bugs across WebGL and WebCore scheduling APIs
Subscribe to read more