CVE-2026-117917
Overview
Background
- `BufferTextureData` / `ShmemHolder`
- WebRender/compositor objects backing a shared-memory texture; the holder owns the shmem and must be released on the allocator’s own thread.
- `AddUserData` / `UserDataKey`
- a DrawTarget key/value store keyed by the address of a
UserDataKey; the key must be a stable, shared address. - `NS_ProxyRelease`
- the safe primitive that releases a refcounted object on a specified thread even if dispatch to that thread fails.
Root Cause Analysis
The patch makes three coupled changes to ShmemTextureData, and the diff leaves two candidate root causes for the reported invalid pointer. First, BufferTextureData::BorrowDrawTarget now calls OnBorrowDrawTarget(dt) — the registration that attaches the ShmemHolder keep-alive user data to the returned DrawTarget; if that registration was missing on this path before, a borrowed DrawTarget could use a ShmemHolder that was already freed (a lifetime use-after-free, and the likely primary cause). Second, DeleteShmemHolderUserData took the allocator thread as a raw auto* eventTarget and only re-dispatched the holder’s release off-thread; if Dispatch failed, the moved holder’s refcount drop released it on the wrong (current) thread — the new comment reads “Do not release the holder on the wrong thread if dispatch fails.”
The fix holds the target in an nsCOMPtr<nsIEventTarget> and always routes the release through NS_ProxyRelease, and also replaces the per-instance mShmemHolderKey with a stable static sShmemHolderKey (a UserDataKey must be a stable address). (Which candidate is the exploited primary — missing keep-alive registration vs wrong-thread release — is not decidable from the diff alone, and the reachability of the threading path depends on another caller of OnBorrowDrawTarget, so it is inferred.)
NS_ProxyRelease with a stable key.Attack Path
- ENTRY
Web content drives WebRender to allocate a shared-memory-backed texture (
ShmemTextureData) and obtain draw targets. - TRIGGER
Cause the DrawTarget’s user data to be torn down (
DeleteShmemHolderUserData) under thread conditions where the dispatch path is not taken or fails. - MISSING CHECK The holder is released through a raw event-target pointer without a held reference and without a failure-safe proxy release, on a thread that may not own it.
- PRIMITIVE
A
ShmemHolderis freed/used off its owning thread, yielding an invalid pointer / use-after-free in the compositor. (Exact racing sequence inferred; the patch shows the unsafe release and its correction.) - IMPACT Memory corruption in the GPU/compositor process reachable from content.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifgfx/layers/BufferTexture.cpp |
modified |
Files Changed
gfx/layers/BufferTexture.cpp
Audit Directions
- Cross-thread refcounted releaseAudit teardown callbacks that call
->Dispatchconditionally; preferNS_ProxyReleasewith a heldnsCOMPtrtarget so a failed dispatch cannot release on the wrong thread. - UserDataKey identityFlag any
AddUserDatawhose key is a non-static member; keys must be stable addresses.
Patch
diff --git a/gfx/layers/BufferTexture.cpp b/gfx/layers/BufferTexture.cpp
index afcfa307d33..0ff1518adc6 100644
--- a/gfx/layers/BufferTexture.cpp
+++ b/gfx/layers/BufferTexture.cpp
@@ -15,6 +15,7 @@
#include "mozilla/layers/ISurfaceAllocator.h"
#include "mozilla/layers/ImageDataSerializer.h"
#include "mozilla/layers/TextureForwarder.h"
+#include "nsProxyRelease.h"
#ifdef MOZ_WIDGET_GTK
# include "gfxPlatformGtk.h"
@@ -134,7 +135,6 @@ class ShmemTextureData : public BufferTextureData {
protected:
const RefPtr<ShmemHolder> mShmemHolder;
- gfx::UserDataKey mShmemHolderKey = {0};
};
BufferTextureData* BufferTextureData::Create(
@@ -291,24 +291,24 @@ struct ShmemHolderUserData {
void DeleteShmemHolderUserData(void* aClosure) {
ShmemHolderUserData* data = reinterpret_cast<ShmemHolderUserData*>(aClosure);
- auto* eventTarget = data->mShmemHolder->mAllocator->GetThread();
+ nsCOMPtr<nsIEventTarget> eventTarget =
+ data->mShmemHolder->mAllocator->GetThread();
- if (!eventTarget->IsOnCurrentThread()) {
- RefPtr<Runnable> runnable =
- NS_NewRunnableFunction("DeleteShmemHolderUserData::Runnable",
- [wrapper = std::move(data->mShmemHolder)]() {});
- eventTarget->Dispatch(runnable.forget());
- }
+ // Do not release the holder on the wrong thread if dispatch fails.
+ NS_ProxyRelease("DeleteShmemHolderUserData", eventTarget,
+ data->mShmemHolder.forget());
delete data;
}
+static gfx::UserDataKey sShmemHolderKey = {0};
+
void ShmemTextureData::OnBorrowDrawTarget(gfx::DrawTarget* aDrawTarget) {
if (!aDrawTarget) {
return;
}
auto* data = new ShmemHolderUserData(mShmemHolder);
- aDrawTarget->AddUserData(&mShmemHolderKey, data, DeleteShmemHolderUserData);
+ aDrawTarget->AddUserData(&sShmemHolderKey, data, DeleteShmemHolderUserData);
}
already_AddRefed<gfx::DrawTarget> BufferTextureData::BorrowDrawTarget() {
@@ -341,6 +341,7 @@ already_AddRefed<gfx::DrawTarget> BufferTextureData::BorrowDrawTarget() {
<< (int)mMoz2DBackend;
}
+ OnBorrowDrawTarget(dt);
return dt.forget();
}