High firefox Sandbox Escape 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionSandbox escape due to invalid pointer in the Disability Access APIs component
ComponentCore
Bug ClassSandbox Escape
Tracker2050627
Fix commit05534deead58 (firefox) +12/-19
CISA KEVNot listed
CreditedOskar L
Disclosed2026-07-21

Background

`RemoteAccessible`
the parent-process proxy object representing an accessibility node that actually lives in a content process.
`DocAccessibleParent`
the parent-process IPC endpoint that receives serialized accessibility node data from a content child process and materializes it.
`uiaRawElmProvider`
the Windows UIA (UI Automation) provider base class that MSAA/IA2 accessible objects inherit from to expose UIA control patterns.
`GetPatternFromDerived`
a helper template that produced a UIA pattern interface (e.g. IGridProvider, ITableProvider) for the current accessible object.

Root Cause Analysis

In uiaRawElmProvider::GetPatternProvider, when a UIA client requested a grid or table pattern, the code decided the object’s concrete C++ type from a role predicate such as acc->IsTable() or acc->IsTableCell() and then called GetPatternFromDerived<Derived, Interface>(), which performed a raw static_cast<Derived*> of this to a type like ia2AccessibleTable or ia2AccessibleTableCell. The violated invariant is that a runtime role/generic-type flag does not guarantee the object is actually an instance of the assumed derived class, so the unchecked downcast could yield an invalid pointer and type confusion. This was reachable because the underlying accessible could be a RemoteAccessible reconstructed from attacker-controlled IPC data in DocAccessibleParent::CreateAcc, where the received Type()/GenericTypes() combination was only partially validated.

The fix in GetPatternFromDerived removes the Derived template parameter and the static_cast chain entirely, instead calling QueryInterface(__uuidof(Interface), ...), which returns the pattern interface only if the object genuinely implements it. Complementarily, DocAccessibleParent::CreateAcc now rejects additional invalid incoming type combinations (eRootType, eApplicationType, and eImageType combined with eHyperText) so a compromised content process cannot forge an accessible whose declared type mismatches its real class. Together these ensure the pointer used for a UIA pattern is always type-correct.

Key insight
The single core mistake was trusting a runtime role flag as proof of a C++ object’s concrete type and performing an unchecked static_cast on it; the fix replaces that downcast with a QueryInterface runtime-type check and hardens the IPC validation that let an attacker create a type-mismatched accessible in the first place.

Attack Path

  1. Compromise a content process The attacker first gains code execution in a sandboxed content process (the standard precondition for a sandbox-escape bug).
  2. Forge accessibility data over IPC The content process sends crafted accessible node data to DocAccessibleParent::CreateAcc with a Type()/GenericTypes() combination that produces an object whose reported role does not match its real class.
  3. Trigger a UIA pattern query With accessibility active, a UIA request reaches uiaRawElmProvider::GetPatternProvider for a grid/table pattern on that object.
  4. Force the bad downcast The role predicate (IsTable/IsTableCell) passes, so GetPatternFromDerived static_casts the object to ia2AccessibleTable/ia2AccessibleTableCell, producing an invalid pointer.
  5. Exploit the invalid pointer Subsequent method calls through that mistyped interface operate on attacker-influenced memory layout in the privileged process, enabling the escape.

Impact Assessment

An attacker who already controls a content process gains an invalid/type-confused pointer dereference in the higher-privileged parent (browser) process on Windows, which is the basis for escaping the content sandbox. Preconditions are a compromised content process and an active accessibility/UIA client path that queries table or grid patterns. The result is corruption or misuse of parent-process memory rather than mere content-process crashes.

Files Changed

  • accessible/ipc/DocAccessibleParent.cpp
  • accessible/windows/uia/uiaRawElmProvider.cpp
  • accessible/windows/uia/uiaRawElmProvider.h

Audit Directions

  • Role-flag-driven `static_cast`
    Search other accessibility and UIA code for downcasts (static_cast) whose target type is chosen from a runtime role or generic-type predicate rather than a verified concrete type, and replace them with QueryInterface or a checked cast.
  • IPC accessible-type validation
    Audit every path that builds parent-process objects from child-supplied Type()/GenericTypes() data (e.g. around DocAccessibleParent::CreateAcc) to confirm all invalid or impossible type combinations are rejected.
  • Cross-process trust of proxy types
    Review UIA/MSAA/IA2 provider methods that assume a RemoteAccessible matches a specific C++ subclass, since remote proxies carry attacker-influenced type metadata.
diff --git a/accessible/ipc/DocAccessibleParent.cpp b/accessible/ipc/DocAccessibleParent.cpp
index 5190c2875cb..c0e4fd521d7 100644
--- a/accessible/ipc/DocAccessibleParent.cpp
+++ b/accessible/ipc/DocAccessibleParent.cpp
@@ -295,7 +295,9 @@ RemoteAccessible* DocAccessibleParent::CreateAcc(
     return nullptr;
   }
 
-  if (aAccData.GenericTypes() & eDocument) {
+  if (aAccData.GenericTypes() & eDocument || aAccData.Type() == eRootType ||
+      aAccData.Type() == eApplicationType ||
+      (aAccData.Type() == eImageType && aAccData.GenericTypes() & eHyperText)) {
     MOZ_ASSERT_UNREACHABLE("Invalid acc type");
     return nullptr;
   }
diff --git a/accessible/windows/uia/uiaRawElmProvider.cpp b/accessible/windows/uia/uiaRawElmProvider.cpp
index 02e59f556ae..f29cd9b3ff0 100644
--- a/accessible/windows/uia/uiaRawElmProvider.cpp
+++ b/accessible/windows/uia/uiaRawElmProvider.cpp
@@ -11,9 +11,6 @@
 #include "AccessibleWrap.h"
 #include "ApplicationAccessible.h"
 #include "ARIAMap.h"
-#include "ia2AccessibleHypertext.h"
-#include "ia2AccessibleTable.h"
-#include "ia2AccessibleTableCell.h"
 #include "LocalAccessible-inl.h"
 #include "mozilla/a11y/Compatibility.h"
 #include "mozilla/a11y/RemoteAccessible.h"
@@ -474,14 +471,13 @@ uiaRawElmProvider::GetPatternProvider(
       return S_OK;
     case UIA_GridPatternId:
       if (acc->IsTable()) {
-        auto grid = GetPatternFromDerived<ia2AccessibleTable, IGridProvider>();
+        auto grid = GetPatternFromDerived<IGridProvider>();
         grid.forget(aPatternProvider);
       }
       return S_OK;
     case UIA_GridItemPatternId:
       if (acc->IsTableCell()) {
-        auto item =
-            GetPatternFromDerived<ia2AccessibleTableCell, IGridItemProvider>();
+        auto item = GetPatternFromDerived<IGridItemProvider>();
         item.forget(aPatternProvider);
       }
       return S_OK;
@@ -538,15 +534,13 @@ uiaRawElmProvider::GetPatternProvider(
       return S_OK;
     case UIA_TablePatternId:
       if (acc->IsTable()) {
-        auto table =
-            GetPatternFromDerived<ia2AccessibleTable, ITableProvider>();
+        auto table = GetPatternFromDerived<ITableProvider>();
         table.forget(aPatternProvider);
       }
       return S_OK;
     case UIA_TableItemPatternId:
       if (acc->IsTableCell()) {
-        auto item =
-            GetPatternFromDerived<ia2AccessibleTableCell, ITableItemProvider>();
+        auto item = GetPatternFromDerived<ITableItemProvider>();
         item.forget(aPatternProvider);
       }
       return S_OK;
@@ -1527,14 +1521,11 @@ bool uiaRawElmProvider::HasValuePattern() const {
   return roleMapEntry && roleMapEntry->Is(nsGkAtoms::textbox);
 }
 
-template <class Derived, class Interface>
+template <class Interface>
 RefPtr<Interface> uiaRawElmProvider::GetPatternFromDerived() {
-  // MsaaAccessible inherits from uiaRawElmProvider. Derived
-  // inherits from MsaaAccessible and Interface. The compiler won't let us
-  // directly static_cast to Interface, hence the intermediate casts.
-  auto* msaa = static_cast<MsaaAccessible*>(this);
-  auto* derived = static_cast<Derived*>(msaa);
-  return derived;
+  RefPtr<Interface> provider;
+  QueryInterface(__uuidof(Interface), getter_AddRefs(provider));
+  return provider;
 }
 
 bool uiaRawElmProvider::HasSelectionItemPattern() {
diff --git a/accessible/windows/uia/uiaRawElmProvider.h b/accessible/windows/uia/uiaRawElmProvider.h
index 89fb4fd7626..2e69781bf73 100644
--- a/accessible/windows/uia/uiaRawElmProvider.h
+++ b/accessible/windows/uia/uiaRawElmProvider.h
@@ -202,7 +202,7 @@ class uiaRawElmProvider : public IAccessibleEx,
   bool HasTogglePattern();
   bool HasExpandCollapsePattern();
   bool HasValuePattern() const;
-  template <class Derived, class Interface>
+  template <class Interface>
   RefPtr<Interface> GetPatternFromDerived();
   bool HasSelectionItemPattern();
   SAFEARRAY* AccRelationsToUiaArray(
Loading diff…