CVE-2026-117970
Overview
Background
- `DocAccessibleParent`
- The parent-process endpoint (
accessible/ipc/DocAccessibleParent.cpp) of the accessibility IPC protocol; it receives accessibility events and mutation events from content processes and maintains the parent-sideRemoteAccessibletree. - Android accessibility monitor / lock
- On Android, accessibility runs across threads (the Java/UI accessibility thread and the IPC thread), so access to the remote accessibility tree is guarded by a monitor obtained via
nsAccessibilityService::GetAndroidMonitor().ACQUIRE_ANDROID_LOCKis the macro that takes that monitor for the scope. - `AssertCurrentThreadOwns()`
- A debug assertion that the calling thread already holds the given monitor, used to document and enforce the locking contract at a point that assumes the lock is held.
- `FireEvent` / `RecvRequestAckMutationEvents`
FireEvent(RemoteAccessible*, uint32_t)dispatches accessibility events and walksaAcc’s children (ChildCount());RecvRequestAckMutationEvents()is an IPC message handler that touches shutdown and tree-initialization state (mShutdown,mIsInitialTreeDone).- Race condition
- Advisory class: concurrent access to the remote accessibility tree from the IPC thread and the Android accessibility thread without consistent locking.
Root Cause Analysis
The patch closes a locking gap on Android in two places. In DocAccessibleParent::FireEvent, it adds (under #if defined(ANDROID)) nsAccessibilityService::GetAndroidMonitor().AssertCurrentThreadOwns(); at function entry, asserting that callers hold the Android monitor before FireEvent walks the accessibility tree (aAcc->ChildCount() for reorder events). More substantively, in the IPC handler DocAccessibleParent::RecvRequestAckMutationEvents, it adds ACQUIRE_ANDROID_LOCK at the top, so the handler now takes the Android monitor before touching mShutdown, mIsInitialTreeDone, and the mutation/tree state. Pre-fix, RecvRequestAckMutationEvents ran without acquiring that lock, so its reads/writes of the remote accessibility tree and the shutdown/initial-tree flags could race with the Android accessibility thread (which does hold the monitor) and with other mutation handling. The violated invariant is that all access to DocAccessibleParent’s remote tree and lifecycle flags on Android must occur under the Android monitor; the handler violated it.
The fix makes the acquisition explicit in the handler and adds the AssertCurrentThreadOwns assertion in FireEvent to enforce the contract on the event-dispatch path. The advisory’s “race condition in Disability Access APIs” matches the diff: this is a missing-lock data race, not a memory-corruption bug visible in the diff (though a race on a tree being torn down can degenerate into a UAF — inferred, not shown).
RecvRequestAckMutationEvents touched the remote accessibility tree and shutdown/initial-tree flags without holding the Android monitor that guards them, racing the Android accessibility thread; the fix acquires the lock in the handler and asserts ownership on the FireEvent dispatch path.Attack Path
- Android target
The bug is gated on
ANDROIDbuilds where accessibility spans the IPC thread and the Android accessibility thread. - Drive accessibility IPC
A content process (or content that influences accessibility state) causes mutation events and ACK requests, invoking
RecvRequestAckMutationEventson the parent IPC thread without the monitor (inferred reachability from a web page that mutates accessible content). - Concurrent Android-thread access
Simultaneously the Android accessibility thread accesses the same
DocAccessibleParenttree/flags under the monitor. - Race the unlocked handler
The unlocked handler reads/writes
mShutdown/mIsInitialTreeDoneand tree state concurrently with the locked thread, producing inconsistent state or a torn read. - Observe/trigger corruption Racing a shutdown or tree-rebuild can leave the tree in an inconsistent or partially-freed state.
- Primitive achieved A data race on the remote accessibility tree/lifecycle flags; potential escalation to use-after-free of accessibility objects is inferred and not shown by the diff.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifaccessible/ipc/DocAccessibleParent.cpp |
modified |
Files Changed
accessible/ipc/DocAccessibleParent.cpp
Audit Directions
- Unlocked IPC handlers on AndroidAudit every
Recv*handler inDocAccessibleParent(and related accessibility parent actors) for ones that touch the remote tree or lifecycle flags withoutACQUIRE_ANDROID_LOCK. - Lock-ownership contractsLook for other tree-walking helpers (like
FireEvent) that assume the monitor is held but lacked anAssertCurrentThreadOwnsguard, to find callers that enter them unlocked. - Race-to-UAF escalationReview whether any of these racy paths can run concurrently with
DocAccessibleParentshutdown/teardown, where the data race could become a use-after-free ofRemoteAccessibleobjects.
Patch
diff --git a/accessible/ipc/DocAccessibleParent.cpp b/accessible/ipc/DocAccessibleParent.cpp
index 63b54079bab..2ae68a9dfe1 100644
--- a/accessible/ipc/DocAccessibleParent.cpp
+++ b/accessible/ipc/DocAccessibleParent.cpp
@@ -540,6 +540,9 @@ mozilla::ipc::IPCResult DocAccessibleParent::RecvEvent(
void DocAccessibleParent::FireEvent(RemoteAccessible* aAcc,
const uint32_t& aEventType) {
+#if defined(ANDROID)
+ nsAccessibilityService::GetAndroidMonitor().AssertCurrentThreadOwns();
+#endif
if (aEventType == nsIAccessibleEvent::EVENT_REORDER ||
aEventType == nsIAccessibleEvent::EVENT_INNER_REORDER) {
uint32_t count = aAcc->ChildCount();
@@ -750,6 +753,7 @@ mozilla::ipc::IPCResult DocAccessibleParent::RecvMutationEvents(
}
mozilla::ipc::IPCResult DocAccessibleParent::RecvRequestAckMutationEvents() {
+ ACQUIRE_ANDROID_LOCK
if (!mShutdown) {
if (!mIsInitialTreeDone) {
// This is the first request for an ACK, which means we now have the