CVE-2026-74947
Overview
Background
- `DrawTargetWebgl`
- the WebGL-accelerated Moz2D drawing backend that renders 2D canvas content on the GPU.
- `Path`
- a Moz2D geometry object whose concrete subclass is determined by its
BackendType(e.g.BackendType::SKIA). - `BackendType::SKIA`
- the enum value marking a
Pathas a Skia-backedPathSkiawhose internalSkPaththe accelerated filter code needs. - Filter deferral
- the mechanism (
DeferFilterInput/ResolveFilterInputAccel) that captures a fill/stroke as filter input to be replayed later on the WebGL target.
Root Cause Analysis
Both DrawTargetWebgl::DeferFilterInput and SharedContextWebgl::ResolveFilterInputAccel accepted a const Path* aPath and stored or re-used it (via do_AddRef((Path*)aPath) into a FilterNodeDeferInputWebgl) without confirming it was non-null or actually a Skia-backed path. The downstream accelerated path handling assumes the geometry is a PathSkia and reaches into its Skia representation, so an invariant — “an accelerated filter input path is a live BackendType::SKIA path” — was never checked at the entry points. When aPath was null, or was a Path created for a different backend, the code dereferenced or reinterpreted an object that did not have the expected layout, yielding an invalid pointer and type confusion.
The fix adds !aPath || aPath->GetBackendType() != BackendType::SKIA guards to both entry points, returning nullptr to fall back to the non-accelerated path instead of proceeding with an untrusted pointer. This works because it enforces the backend invariant before any Skia-specific dereference occurs.
Path* as if it were guaranteed to be a live BackendType::SKIA object without a null-or-type check; the fix restores that guarantee by validating aPath and its GetBackendType() before the pointer is stored or dereferenced.Attack Path
- Reach the accelerated filter path
Craft canvas content that draws a filtered fill or stroke onto a WebGL-accelerated
DrawTargetWebgl, routing intoDeferFilterInput. - Supply a mismatched path
Arrange for the path argument to be null or a non-Skia
Pathso the backend-type invariant is violated. - Store the bad pointer
DeferFilterInputwraps the unvalidatedaPathinto aFilterNodeDeferInputWebgl, retaining an invalid or type-confused pointer. - Trigger resolution
Replay/resolve the deferred filter so
ResolveFilterInputAcceltreats the object as a Skia path and dereferences its unexpected memory layout. - Corrupt state The resulting invalid pointer use produces memory corruption exploitable toward privilege escalation.
Impact Assessment
DrawTarget. The metadata rates this a high-severity privilege escalation. Preconditions are that the accelerated WebGL canvas backend is active and the attacker can drive filtered draw operations with a null or non-Skia path.Files Changed
dom/canvas/DrawTargetWebgl.cpp
Audit Directions
- Unvalidated backend castsFlag any site that treats a
Path*/SourceSurface*as a specific backend (e.g. callsdo_AddRef((Path*)...)or reads Skia internals) without first checkingGetBackendType(). - Nullable path parametersReview Moz2D entry points taking
const Path* aPathfor missing null checks before the pointer is stored or dereferenced. - Deferred-object lifetimesAudit deferral wrappers like
FilterNodeDeferInputWebglthat hold caller pointers across time, ensuring the object’s type and validity are guaranteed at both capture and resolution.
Patch
diff --git a/dom/canvas/DrawTargetWebgl.cpp b/dom/canvas/DrawTargetWebgl.cpp
index 19285706498..4bb0e00a8c4 100644
--- a/dom/canvas/DrawTargetWebgl.cpp
+++ b/dom/canvas/DrawTargetWebgl.cpp
@@ -4189,7 +4189,8 @@ already_AddRefed<TextureHandle> SharedContextWebgl::ResolveFilterInputAccel(
const IntRect& aSourceRect, const Matrix& aDestTransform,
const DrawOptions& aOptions, const StrokeOptions* aStrokeOptions,
SurfaceFormat aFormat) {
- if (SupportsDrawOptions(aOptions) != SupportsDrawOptionsStatus::Yes) {
+ if (SupportsDrawOptions(aOptions) != SupportsDrawOptionsStatus::Yes ||
+ !aPath || aPath->GetBackendType() != BackendType::SKIA) {
return nullptr;
}
if (IsContextLost()) {
@@ -6802,6 +6803,9 @@ already_AddRefed<FilterNode> DrawTargetWebgl::DeferFilterInput(
const Path* aPath, const Pattern& aPattern, const IntRect& aSourceRect,
const IntPoint& aDestOffset, const DrawOptions& aOptions,
const StrokeOptions* aStrokeOptions) {
+ if (!aPath || aPath->GetBackendType() != BackendType::SKIA) {
+ return nullptr;
+ }
RefPtr<FilterNode> filter = new FilterNodeDeferInputWebgl(
do_AddRef((Path*)aPath), aPattern, aSourceRect,
GetTransform().PostTranslate(aDestOffset), aOptions, aStrokeOptions);