Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit edff3851 authored by Prabir Pradhan's avatar Prabir Pradhan
Browse files

Change input injection security model to require INJECT_EVENTS permission

Previously, any app could inject input events into the system via the
IInputManager#injectInputEvent API. The injection was only allowed if
the input event targeted a window owned by the same UID as that of the
process calling the API. This had drawbacks metioned in the bug.

Here, we change the input injection security model so that the signature
permission INJECT_EVENTS is required to inject events. This permission
is given to the system and the shell, so input injection can still be
done through the 'adb shell input' command. We also allow injection from
instrumeted processes where the instrumentation source has the
permission. For exmaple, running a test from the shell allows for the
test to inject events.

We also add support for a targeted injection mode, where the input
injection succeeds only if the target window for the event is owned by
the provided UID. This allows us to support injection from the
Instrumentation class, which only allows for injection into windows
owned by the same UID. In contrast to this, injection from the
UiAutomation class will target all windows, including system and spy
windows.

Bug: 207667844
Bug: 194952792
Test: atest inputflinger_tests
Test: atest WindowInputTests
Test: manual with test app: app cannot inject navigation gestures
Change-Id: Ib0d66eff3fc2f061e7c0d3b7e139a841a9bcebc7
parent 23b8a6d6
Loading
Loading
Loading
Loading
+5 −4
Original line number Diff line number Diff line
@@ -562,14 +562,15 @@ public abstract class ActivityManagerInternal {
    public abstract void unregisterProcessObserver(IProcessObserver processObserver);

    /**
     * Checks if there is an unfinished instrumentation that targets the given uid.
     * Gets the uid of the instrumentation source if there is an unfinished instrumentation that
     * targets the given uid.
     *
     * @param uid The uid to be checked for
     *
     * @return True, if there is an instrumentation whose target application uid matches the given
     * uid, false otherwise
     * @return the uid of the instrumentation source, if there is an instrumentation whose target
     * application uid matches the given uid, and {@link android.os.Process#INVALID_UID} otherwise.
     */
    public abstract boolean isUidCurrentlyInstrumented(int uid);
    public abstract int getInstrumentationSourceUid(int uid);

    /** Is this a device owner app? */
    public abstract boolean isDeviceOwner(int uid);
+55 −37
Original line number Diff line number Diff line
@@ -1058,10 +1058,11 @@ public class Instrumentation {
    }
    
    /**
     * Sends the key events corresponding to the text to the app being
     * instrumented.
     * Sends the key events that result in the given text being typed into the currently focused
     * window, and waits for it to be processed.
     *
     * @param text The text to be sent.
     * @see #sendKeySync(KeyEvent)
     */
    public void sendStringSync(String text) {
        if (text == null) {
@@ -1084,11 +1085,12 @@ public class Instrumentation {
    }

    /**
     * Send a key event to the currently focused window/view and wait for it to
     * be processed.  Finished at some point after the recipient has returned
     * from its event processing, though it may <em>not</em> have completely
     * finished reacting from the event -- for example, if it needs to update
     * its display as a result, it may still be in the process of doing that.
     * Sends a key event to the currently focused window, and waits for it to be processed.
     * <p>
     * This method blocks until the recipient has finished handling the event. Note that the
     * recipient may <em>not</em> have completely finished reacting from the event when this method
     * returns. For example, it may still be in the process of updating its display or UI contents
     * upon reacting to the injected event.
     *
     * @param event The event to send to the current focus.
     */
@@ -1116,34 +1118,42 @@ public class Instrumentation {
    }

    /**
     * Sends an up and down key event sync to the currently focused window.
     * Sends up and down key events with the given key code to the currently focused window, and
     * waits for it to be processed.
     * 
     * @param key The integer keycode for the event.
     * @param keyCode The key code for the events to send.
     * @see #sendKeySync(KeyEvent)
     */
    public void sendKeyDownUpSync(int key) {        
        sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, key));
        sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, key));
    public void sendKeyDownUpSync(int keyCode) {
        sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
        sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
    }

    /**
     * Higher-level method for sending both the down and up key events for a
     * particular character key code.  Equivalent to creating both KeyEvent
     * objects by hand and calling {@link #sendKeySync}.  The event appears
     * as if it came from keyboard 0, the built in one.
     * Sends up and down key events with the given key code to the currently focused window, and
     * waits for it to be processed.
     * <p>
     * Equivalent to {@link #sendKeyDownUpSync(int)}.
     *
     * @param keyCode The key code of the character to send.
     * @see #sendKeySync(KeyEvent)
     */
    public void sendCharacterSync(int keyCode) {
        sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
        sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
        sendKeyDownUpSync(keyCode);
    }

    /**
     * Dispatch a pointer event. Finished at some point after the recipient has
     * returned from its event processing, though it may <em>not</em> have
     * completely finished reacting from the event -- for example, if it needs
     * to update its display as a result, it may still be in the process of
     * doing that.
     * Dispatches a pointer event into a window owned by the instrumented application, and waits for
     * it to be processed.
     * <p>
     * If the motion event being injected is targeted at a window that is not owned by the
     * instrumented application, the input injection will fail. See {@link #getUiAutomation()} for
     * injecting events into all windows.
     * <p>
     * This method blocks until the recipient has finished handling the event. Note that the
     * recipient may <em>not</em> have completely finished reacting from the event when this method
     * returns. For example, it may still be in the process of updating its display or UI contents
     * upon reacting to the injected event.
     * 
     * @param event A motion event describing the pointer action.  (As noted in 
     * {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use 
@@ -1155,10 +1165,10 @@ public class Instrumentation {
            event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
        }

        syncInputTransactionsAndInjectEvent(event);
        syncInputTransactionsAndInjectEventIntoSelf(event);
    }

    private void syncInputTransactionsAndInjectEvent(MotionEvent event) {
    private void syncInputTransactionsAndInjectEventIntoSelf(MotionEvent event) {
        final boolean syncBefore = event.getAction() == MotionEvent.ACTION_DOWN
                || event.isFromSource(InputDevice.SOURCE_MOUSE);
        final boolean syncAfter = event.getAction() == MotionEvent.ACTION_UP;
@@ -1169,8 +1179,9 @@ public class Instrumentation {
                        .syncInputTransactions(true /*waitForAnimations*/);
            }

            // Direct the injected event into windows owned by the instrumentation target.
            InputManager.getInstance().injectInputEvent(
                    event, InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT);
                    event, InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT, Process.myUid());

            if (syncAfter) {
                WindowManagerGlobal.getWindowManagerService()
@@ -1182,11 +1193,13 @@ public class Instrumentation {
    }

    /**
     * Dispatch a trackball event. Finished at some point after the recipient has
     * returned from its event processing, though it may <em>not</em> have
     * completely finished reacting from the event -- for example, if it needs
     * to update its display as a result, it may still be in the process of
     * doing that.
     * Dispatches a trackball event into the currently focused window, and waits for it to be
     * processed.
     * <p>
     * This method blocks until the recipient has finished handling the event. Note that the
     * recipient may <em>not</em> have completely finished reacting from the event when this method
     * returns. For example, it may still be in the process of updating its display or UI contents
     * upon reacting to the injected event.
     *
     * @param event A motion event describing the trackball action.  (As noted in 
     * {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use 
@@ -1194,7 +1207,12 @@ public class Instrumentation {
     */
    public void sendTrackballEventSync(MotionEvent event) {
        validateNotAppThread();
        if ((event.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) == 0) {
        if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
            throw new IllegalArgumentException(
                    "Cannot inject pointer events from sendTrackballEventSync()."
                            + " Use sendPointerSync() to inject pointer events.");
        }
        if (!event.isFromSource(InputDevice.SOURCE_CLASS_TRACKBALL)) {
            event.setSource(InputDevice.SOURCE_TRACKBALL);
        }
        InputManager.getInstance().injectInputEvent(event,
+4 −3
Original line number Diff line number Diff line
@@ -57,10 +57,11 @@ interface IInputManager {
    // Temporarily changes the pointer speed.
    void tryPointerSpeed(int speed);

    // Injects an input event into the system.  To inject into windows owned by other
    // applications, the caller must have the INJECT_EVENTS permission.
    // Injects an input event into the system. The caller must have the INJECT_EVENTS permission.
    // The caller can target windows owned by a certain UID by providing a valid UID, or by
    // providing {@link android.os.Process#INVALID_UID} to target all windows.
    @UnsupportedAppUsage
    boolean injectInputEvent(in InputEvent ev, int mode);
    boolean injectInputEvent(in InputEvent ev, int mode, int targetUid);

    VerifiedInputEvent verifyInputEvent(in InputEvent ev);

+40 −7
Original line number Diff line number Diff line
@@ -45,6 +45,7 @@ import android.os.IVibratorStateListener;
import android.os.InputEventInjectionSync;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
@@ -1107,14 +1108,18 @@ public final class InputManager {
        }
    }


    /**
     * Injects an input event into the event system on behalf of an application.
     * Injects an input event into the event system, targeting windows owned by the provided uid.
     *
     * If a valid targetUid is provided, the system will only consider injecting the input event
     * into windows owned by the provided uid. If the input event is targeted at a window that is
     * not owned by the provided uid, input injection will fail and a RemoteException will be
     * thrown.
     *
     * The synchronization mode determines whether the method blocks while waiting for
     * input injection to proceed.
     * <p>
     * Requires {@link android.Manifest.permission.INJECT_EVENTS} to inject into
     * windows that are owned by other applications.
     * Requires the {@link android.Manifest.permission.INJECT_EVENTS} permission.
     * </p><p>
     * Make sure you correctly set the event time and input source of the event
     * before calling this method.
@@ -1125,12 +1130,14 @@ public final class InputManager {
     * {@link android.os.InputEventInjectionSync.NONE},
     * {@link android.os.InputEventInjectionSync.WAIT_FOR_RESULT}, or
     * {@link android.os.InputEventInjectionSync.WAIT_FOR_FINISHED}.
     * @param targetUid The uid to target, or {@link android.os.Process#INVALID_UID} to target all
     *                 windows.
     * @return True if input event injection succeeded.
     *
     * @hide
     */
    @UnsupportedAppUsage
    public boolean injectInputEvent(InputEvent event, int mode) {
    @RequiresPermission(Manifest.permission.INJECT_EVENTS)
    public boolean injectInputEvent(InputEvent event, int mode, int targetUid) {
        if (event == null) {
            throw new IllegalArgumentException("event must not be null");
        }
@@ -1141,12 +1148,38 @@ public final class InputManager {
        }

        try {
            return mIm.injectInputEvent(event, mode);
            return mIm.injectInputEvent(event, mode, targetUid);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }

    /**
     * Injects an input event into the event system on behalf of an application.
     * The synchronization mode determines whether the method blocks while waiting for
     * input injection to proceed.
     * <p>
     * Requires the {@link android.Manifest.permission.INJECT_EVENTS} permission.
     * </p><p>
     * Make sure you correctly set the event time and input source of the event
     * before calling this method.
     * </p>
     *
     * @param event The event to inject.
     * @param mode The synchronization mode.  One of:
     * {@link android.os.InputEventInjectionSync.NONE},
     * {@link android.os.InputEventInjectionSync.WAIT_FOR_RESULT}, or
     * {@link android.os.InputEventInjectionSync.WAIT_FOR_FINISHED}.
     * @return True if input event injection succeeded.
     *
     * @hide
     */
    @RequiresPermission(Manifest.permission.INJECT_EVENTS)
    @UnsupportedAppUsage
    public boolean injectInputEvent(InputEvent event, int mode) {
        return injectInputEvent(event, mode, Process.INVALID_UID);
    }

    /**
     * Verify the details of an {@link android.view.InputEvent} that came from the system.
     * If the event did not come from the system, or its details could not be verified, then this
+3 −3
Original line number Diff line number Diff line
@@ -17234,17 +17234,17 @@ public class ActivityManagerService extends IActivityManager.Stub
        }
        @Override
        public boolean isUidCurrentlyInstrumented(int uid) {
        public int getInstrumentationSourceUid(int uid) {
            synchronized (mProcLock) {
                for (int i = mActiveInstrumentation.size() - 1; i >= 0; i--) {
                    ActiveInstrumentation activeInst = mActiveInstrumentation.get(i);
                    if (!activeInst.mFinished && activeInst.mTargetInfo != null
                            && activeInst.mTargetInfo.uid == uid) {
                        return true;
                        return activeInst.mSourceUid;
                    }
                }
            }
            return false;
            return INVALID_UID;
        }
        @Override
Loading