DO NOT MERGE: Verify KeyEvents in IME

Malicious app can inject fabricated InputEvents into IME's FD which can
do tinker with special shortcuts like switching IME.
With this CL, all KeyEvents with modifiers OR indicated sensitive by IME will be
verified before being dispatched to IME.

For any events that are replayed, verify timestamp.

Bug: 331730488
Test: atest CtsInputMethodTestCases InputMethodServiceTest

Change-Id: I6589cc73839acb6b2c3f9256c664b5db7e1ade8a
Merged-In: I6589cc73839acb6b2c3f9256c664b5db7e1ade8a
diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
index 9a7ccc6..07fda82 100644
--- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
@@ -19,9 +19,11 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.graphics.Rect;
+import android.hardware.input.InputManager;
 import android.os.Bundle;
 import android.os.Looper;
 import android.os.Message;
+import android.os.SystemClock;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.InputChannel;
@@ -41,6 +43,8 @@
 import com.android.internal.view.IInputContext;
 import com.android.internal.view.IInputMethodSession;
 
+import java.util.Objects;
+
 class IInputMethodSessionWrapper extends IInputMethodSession.Stub
         implements HandlerCaller.Callback {
     private static final String TAG = "InputMethodWrapper";
@@ -56,6 +60,7 @@
     private static final int DO_REMOVE_IME_SURFACE = 130;
     private static final int DO_FINISH_INPUT = 140;
     private static final int DO_INVALIDATE_INPUT = 150;
+    private final Context mContext;
 
 
     @UnsupportedAppUsage
@@ -66,6 +71,7 @@
 
     public IInputMethodSessionWrapper(Context context,
             InputMethodSession inputMethodSession, InputChannel channel) {
+        mContext = context;
         mCaller = new HandlerCaller(context, null,
                 this, true /*asyncHandler*/);
         mInputMethodSession = inputMethodSession;
@@ -232,6 +238,8 @@
     }
     private final class ImeInputEventReceiver extends InputEventReceiver
             implements InputMethodSession.EventCallback {
+        // Time after which a KeyEvent is invalid
+        private static final long KEY_EVENT_ALLOW_PERIOD_MS = 100L;
         private final SparseArray<InputEvent> mPendingEvents = new SparseArray<InputEvent>();
 
         public ImeInputEventReceiver(InputChannel inputChannel, Looper looper) {
@@ -246,10 +254,27 @@
                 return;
             }
 
+            KeyEvent keyEvent = null;
+            if (event != null && event instanceof KeyEvent) {
+                keyEvent = (KeyEvent) event;
+                if (hasKeyModifiers(keyEvent)) {
+                    // any KeyEvent with modifiers (e.g. Ctrl/Alt/Fn) must be verified that
+                    // they originated from system.
+                    InputManager im = mContext.getSystemService(InputManager.class);
+                    Objects.requireNonNull(im);
+                    final long age = SystemClock.uptimeMillis() - keyEvent.getEventTime();
+                    if (age >= KEY_EVENT_ALLOW_PERIOD_MS || im.verifyInputEvent(keyEvent) == null) {
+                        Log.w(TAG, "Unverified or Invalid KeyEvent injected into IME. Dropping "
+                                + keyEvent);
+                        finishInputEvent(event, false /* handled */);
+                        return;
+                    }
+                }
+            }
+
             final int seq = event.getSequenceNumber();
             mPendingEvents.put(seq, event);
-            if (event instanceof KeyEvent) {
-                KeyEvent keyEvent = (KeyEvent)event;
+            if (keyEvent != null) {
                 mInputMethodSession.dispatchKeyEvent(seq, keyEvent, this);
             } else {
                 MotionEvent motionEvent = (MotionEvent)event;
@@ -270,5 +295,15 @@
                 finishInputEvent(event, handled);
             }
         }
+
+        private boolean hasKeyModifiers(KeyEvent event) {
+            if (event.hasNoModifiers()) {
+                return false;
+            }
+            return event.isCtrlPressed()
+                    || event.isAltPressed()
+                    || event.isFunctionPressed()
+                    || event.isMetaPressed();
+        }
     }
 }