CVE-2025-22438 AI-generated analysis
platform/frameworks/native, inputflinger InputDispatcher (services/inputflinger/dispatcher)
In InputDispatcher::afterKeyEventLockedInterruptable, take a std::shared_ptr<EventEntry> copy and a local copy of hasForegroundTarget() from the DispatchEntry before the lock is released for policy calls, so the KeyEntry stays valid and the DispatchEntry is no longer dereferenced even if a concurrent removeInputChannel drains the connection's waitQueue and deletes the DispatchEntry.
Root cause
doDispatchCycleFinishedCommand looked up a DispatchEntry* in connection->waitQueue, built a KeyEntry& from dispatchEntry->eventEntry, and passed both to afterKeyEventLockedInterruptable. That function releases the lock to call into policy. While the lock is released, a removeInputChannel binder call can run. It drains the waitQueue and deletes the DispatchEntry. If the DispatchEntry held the last remaining reference to the EventEntry, the KeyEntry is freed too. The function then kept using the KeyEntry& and dispatchEntry->hasForegroundTarget(), which could point to freed memory. Per the commit message, in the observed flow the KeyEntry had been allocated by synthesizeCancelationEvents during a setFocusedWindow call.
How the fix works
- services/inputflinger/dispatcher/InputDispatcher.cpp:afterKeyEventLockedInterruptable: the signature changes to (connection, DispatchEntry* dispatchEntry, bool handled), dropping the KeyEntry& parameter. At function entry, before any lock release, the function copies `std::shared_ptr<EventEntry> eventEntry = dispatchEntry->eventEntry` (a strong reference held for the whole function) and `const bool hasForegroundTarget = dispatchEntry->hasForegroundTarget()`. It then builds `KeyEntry& keyEntry` from the local shared_ptr and sets `dispatchEntry = nullptr`.
- services/inputflinger/dispatcher/InputDispatcher.cpp:afterKeyEventLockedInterruptable: the later check `if (handled || !dispatchEntry->hasForegroundTarget())` now reads the local `hasForegroundTarget` copy.
- services/inputflinger/dispatcher/InputDispatcher.cpp:doDispatchCycleFinishedCommand: the KEY branch now calls afterKeyEventLockedInterruptable(connection, dispatchEntry, handled) and no longer builds a KeyEntry& itself.
- services/inputflinger/dispatcher/InputDispatcher.h: the declaration of afterKeyEventLockedInterruptable is updated to the new three-argument signature.
- The commit message also says the queued doDispatchCycleFinishedCommand should hold a strong pointer to the Connection rather than a reference. The Android 14 diff contains no such change, and the material does not show how the command captures the Connection on this branch.
Completeness
- Covers the KEY path only: afterKeyEventLockedInterruptable now holds a shared_ptr to the EventEntry and no longer dereferences dispatchEntry after the lock may be released.
- The MOTION path is untouched: afterMotionEventLockedInterruptable still receives DispatchEntry* and a MotionEntry& built from dispatchEntry->eventEntry in doDispatchCycleFinishedCommand. The material does not show whether that function releases the lock.
- After the policy calls, doDispatchCycleFinishedCommand already looks up the DispatchEntry again with connection->findWaitQueueEntry(seq) before erasing it, so the dequeue step was already robust to the waitQueue being drained.
- No test files are modified in the diff. The commit only cites `atest --host inputflinger_tests`.
Behavior changes
- No functional change is evident: the EventEntry may live until afterKeyEventLockedInterruptable returns, and the same decisions are made from copied values.
Per major
- Android 14: same fix: commit 7ac747cb442d (cherry-picked, Merged-In Ibea7117e4c85cd1e98bbd01872ce249cbb2d54bd) in android-security-14.0.0_r19. At android-security-14.0.0_r29, the fixed call `afterKeyEventLockedInterruptable(connection, dispatchEntry, handled)` and the three-argument declaration are present (resolver: 6/8 lines of the patched region present).
- Android 15: not affected per the bulletin (not listed). 0/8 lines of the patched region are present at android-security-15.0.0_r17. The material does not show the code there.
- Android 16: not affected per the bulletin (not listed). 0/8 lines of the patched region are present at android-security-16.0.0_r8.
- Android 17: not affected per the bulletin (not listed). 0/8 lines of the patched region are present at android-security-17.0.0_r1.
Unknowns
- Whether afterMotionEventLockedInterruptable releases the lock and is exposed to the same lifetime issue.
- How the queued doDispatchCycleFinishedCommand captures the Connection on Android 14, given the commit message's strong-pointer change.
- How the Android 15/16/17 code looks, given that the bulletin lists '15-next' and the patched region is absent there.
- Whether the Android 13 fix (the bulletin lists 13 as affected) is identical.
- The exact security impact beyond the bulletin's High/EoP rating and the commit's description of a crash.