CVE-2026-16367
Overview
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.
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
- Compromise a content process The attacker first gains code execution in a sandboxed content process (the standard precondition for a sandbox-escape bug).
- Forge accessibility data over IPC
The content process sends crafted accessible node data to
DocAccessibleParent::CreateAccwith aType()/GenericTypes()combination that produces an object whose reported role does not match its real class. - Trigger a UIA pattern query
With accessibility active, a UIA request reaches
uiaRawElmProvider::GetPatternProviderfor a grid/table pattern on that object. - Force the bad downcast
The role predicate (
IsTable/IsTableCell) passes, soGetPatternFromDerivedstatic_casts the object toia2AccessibleTable/ia2AccessibleTableCell, producing an invalid pointer. - 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
Files Changed
accessible/ipc/DocAccessibleParent.cppaccessible/windows/uia/uiaRawElmProvider.cppaccessible/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 withQueryInterfaceor a checked cast. - IPC accessible-type validationAudit every path that builds parent-process objects from child-supplied
Type()/GenericTypes()data (e.g. aroundDocAccessibleParent::CreateAcc) to confirm all invalid or impossible type combinations are rejected. - Cross-process trust of proxy typesReview UIA/MSAA/IA2 provider methods that assume a
RemoteAccessiblematches a specific C++ subclass, since remote proxies carry attacker-influenced type metadata.
Patch
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(