High firefox Logic Error 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionPrivilege escalation due to invalid pointer in the Graphics component
ComponentDOM
Bug ClassLogic Error
Tracker2060010
Fix commit46db1e662d01 (firefox) +5/-1
CISA KEVNot listed
Creditednavapon
Disclosed2026-08-18

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 Path as a Skia-backed PathSkia whose internal SkPath the 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.

Key insight
The single core mistake was consuming a caller-supplied 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

  1. Reach the accelerated filter path Craft canvas content that draws a filtered fill or stroke onto a WebGL-accelerated DrawTargetWebgl, routing into DeferFilterInput.
  2. Supply a mismatched path Arrange for the path argument to be null or a non-Skia Path so the backend-type invariant is violated.
  3. Store the bad pointer DeferFilterInput wraps the unvalidated aPath into a FilterNodeDeferInputWebgl, retaining an invalid or type-confused pointer.
  4. Trigger resolution Replay/resolve the deferred filter so ResolveFilterInputAccel treats the object as a Skia path and dereferences its unexpected memory layout.
  5. Corrupt state The resulting invalid pointer use produces memory corruption exploitable toward privilege escalation.

Impact Assessment

An attacker who controls canvas/graphics rendering gains use of an invalid or type-confused pointer inside the graphics component, corrupting memory in the process running the WebGL 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 casts
    Flag any site that treats a Path*/SourceSurface* as a specific backend (e.g. calls do_AddRef((Path*)...) or reads Skia internals) without first checking GetBackendType().
  • Nullable path parameters
    Review Moz2D entry points taking const Path* aPath for missing null checks before the pointer is stored or dereferenced.
  • Deferred-object lifetimes
    Audit deferral wrappers like FilterNodeDeferInputWebgl that hold caller pointers across time, ensuring the object’s type and validity are guaranteed at both capture and resolution.
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);
Loading diff…