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

Commit 28f26513 authored by Rhed Jao's avatar Rhed Jao
Browse files

Package visibility improvement for the InputMethodManager

A few APIs in the InputMethodManager disclose the installed input
method packages to apps without holding the query package permission.
This CL filters the caller's access to input methods by rules of
package visibility, except for the currently selected input method.

Updated APIs:
  - getInputMethodList
  - getEnabledInputMethodList
  - getEnabledInputMethodSubtypeList
  - getShortcutInputMethodsAndSubtypes

Bug: 179783492
Bug: 179783499
Bug: 216823971
Test: atest CtsInputMethodServiceHostTestCases
Test: atest CtsInputMethodTestCases
Test: atest CtsInputMethodTestCases32
Test: atest CtsAppEnumerationTestCases
Change-Id: I698b54192b827abeba475df90e099aefe6dd0e18
parent a918fd17
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -265,6 +265,12 @@ import java.util.function.Consumer;
 * they can switch to it, to confirm with the system that they know about it
 * and want to make it available for use.</p>
 * </ul>
 *
 * <p>If your app targets Android 11 (API level 30) or higher, the methods in
 * this class each return a filtered result by the rules of
 * <a href="/training/basics/intents/package-visibility">package visibility</a>,
 * except for the currently connected IME. Apps having a query for the
 * {@link InputMethod#SERVICE_INTERFACE} see all IMEs.</p>
 */
@SystemService(Context.INPUT_METHOD_SERVICE)
@RequiresFeature(PackageManager.FEATURE_INPUT_METHODS)
+68 −16
Original line number Diff line number Diff line
@@ -2045,9 +2045,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
            if (resolvedUserIds.length != 1) {
                return Collections.emptyList();
            }
            final int callingUid = Binder.getCallingUid();
            final long ident = Binder.clearCallingIdentity();
            try {
                return getInputMethodListLocked(resolvedUserIds[0], directBootAwareness);
                return getInputMethodListLocked(
                        resolvedUserIds[0], directBootAwareness, callingUid);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
@@ -2066,9 +2068,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
            if (resolvedUserIds.length != 1) {
                return Collections.emptyList();
            }
            final int callingUid = Binder.getCallingUid();
            final long ident = Binder.clearCallingIdentity();
            try {
                return getEnabledInputMethodListLocked(resolvedUserIds[0]);
                return getEnabledInputMethodListLocked(resolvedUserIds[0], callingUid);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
@@ -2099,12 +2102,14 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub

    @GuardedBy("ImfLock.class")
    private List<InputMethodInfo> getInputMethodListLocked(@UserIdInt int userId,
            @DirectBootAwareness int directBootAwareness) {
            @DirectBootAwareness int directBootAwareness, int callingUid) {
        final ArrayList<InputMethodInfo> methodList;
        final InputMethodSettings settings;
        if (userId == mSettings.getCurrentUserId()
                && directBootAwareness == DirectBootAwareness.AUTO) {
            // Create a copy.
            methodList = new ArrayList<>(mMethodList);
            settings = mSettings;
        } else {
            final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
            methodList = new ArrayList<>();
@@ -2113,19 +2118,31 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
            AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
            queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
                    methodList, directBootAwareness);
            settings = new InputMethodSettings(mContext, methodMap, userId, true /* copyOnWrite */);
        }
        // filter caller's access to input methods
        methodList.removeIf(imi ->
                !canCallerAccessInputMethod(imi.getPackageName(), callingUid, userId, settings));
        return methodList;
    }

    @GuardedBy("ImfLock.class")
    private List<InputMethodInfo> getEnabledInputMethodListLocked(@UserIdInt int userId) {
    private List<InputMethodInfo> getEnabledInputMethodListLocked(@UserIdInt int userId,
            int callingUid) {
        final ArrayList<InputMethodInfo> methodList;
        final InputMethodSettings settings;
        if (userId == mSettings.getCurrentUserId()) {
            return mSettings.getEnabledInputMethodListLocked();
        }
            methodList = mSettings.getEnabledInputMethodListLocked();
            settings = mSettings;
        } else {
            final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
        final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap, userId,
                true);
        return settings.getEnabledInputMethodListLocked();
            settings = new InputMethodSettings(mContext, methodMap, userId, true /* copyOnWrite */);
            methodList = settings.getEnabledInputMethodListLocked();
        }
        // filter caller's access to input methods
        methodList.removeIf(imi ->
                !canCallerAccessInputMethod(imi.getPackageName(), callingUid, userId, settings));
        return methodList;
    }

    @GuardedBy("ImfLock.class")
@@ -2164,10 +2181,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
        }

        synchronized (ImfLock.class) {
            final int callingUid = Binder.getCallingUid();
            final long ident = Binder.clearCallingIdentity();
            try {
                return getEnabledInputMethodSubtypeListLocked(imiId,
                        allowsImplicitlyEnabledSubtypes, userId);
                        allowsImplicitlyEnabledSubtypes, userId, callingUid);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
@@ -2176,7 +2194,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub

    @GuardedBy("ImfLock.class")
    private List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(String imiId,
            boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId) {
            boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId, int callingUid) {
        if (userId == mSettings.getCurrentUserId()) {
            final InputMethodInfo imi;
            String selectedMethodId = getSelectedMethodIdLocked();
@@ -2185,7 +2203,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
            } else {
                imi = mMethodMap.get(imiId);
            }
            if (imi == null) {
            if (imi == null || !canCallerAccessInputMethod(
                    imi.getPackageName(), callingUid, userId, mSettings)) {
                return Collections.emptyList();
            }
            return mSettings.getEnabledInputMethodSubtypeListLocked(
@@ -2198,6 +2217,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
        }
        final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap, userId,
                true);
        if (!canCallerAccessInputMethod(imi.getPackageName(), callingUid, userId, settings)) {
            return Collections.emptyList();
        }
        return settings.getEnabledInputMethodSubtypeListLocked(
                imi, allowsImplicitlyEnabledSubtypes);
    }
@@ -5426,6 +5448,34 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
        return true;
    }

    /**
     * Filter the access to the input method by rules of the package visibility. Return {@code true}
     * if the given input method is the currently selected one or visible to the caller.
     *
     * @param targetPkgName The package name of input method to check.
     * @param callingUid The caller that is going to access the input method.
     * @param userId The user ID where the input method resides.
     * @param settings The input method settings under the given user ID.
     * @return {@code true} if caller is able to access the input method.
     */
    private boolean canCallerAccessInputMethod(@NonNull String targetPkgName, int callingUid,
            @UserIdInt int userId, @NonNull InputMethodSettings settings) {
        final String methodId = settings.getSelectedInputMethod();
        final ComponentName selectedInputMethod = methodId != null
                ? InputMethodUtils.convertIdToComponentName(methodId) : null;
        if (selectedInputMethod != null
                && selectedInputMethod.getPackageName().equals(targetPkgName)) {
            return true;
        }
        final boolean canAccess = !mPackageManagerInternal.filterAppAccess(
                targetPkgName, callingUid, userId);
        if (DEBUG && !canAccess) {
            Slog.d(TAG, "Input method " + targetPkgName
                    + " is not visible to the caller " + callingUid);
        }
        return canAccess;
    }

    private void publishLocalService() {
        LocalServices.addService(InputMethodManagerInternal.class, new LocalServiceImpl());
    }
@@ -5447,14 +5497,15 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
        @Override
        public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
            synchronized (ImfLock.class) {
                return getInputMethodListLocked(userId, DirectBootAwareness.AUTO);
                return getInputMethodListLocked(userId, DirectBootAwareness.AUTO,
                        Process.SYSTEM_UID);
            }
        }

        @Override
        public List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId) {
            synchronized (ImfLock.class) {
                return getEnabledInputMethodListLocked(userId);
                return getEnabledInputMethodListLocked(userId, Process.SYSTEM_UID);
            }
        }

@@ -6084,8 +6135,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
            try (PrintWriter pr = shellCommand.getOutPrintWriter()) {
                for (int userId : userIds) {
                    final List<InputMethodInfo> methods = all
                            ? getInputMethodListLocked(userId, DirectBootAwareness.AUTO)
                            : getEnabledInputMethodListLocked(userId);
                            ? getInputMethodListLocked(
                                    userId, DirectBootAwareness.AUTO, Process.SHELL_UID)
                            : getEnabledInputMethodListLocked(userId, Process.SHELL_UID);
                    if (userIds.length > 1) {
                        pr.print("User #");
                        pr.print(userId);
+14 −0
Original line number Diff line number Diff line
@@ -20,11 +20,13 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserHandleAware;
import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.os.Build;
import android.os.UserHandle;
@@ -1000,4 +1002,16 @@ final class InputMethodUtils {
        }
        return new int[]{sourceUserId};
    }

    /**
     * Convert the input method ID to a component name
     *
     * @param id A unique ID for this input method.
     * @return The component name of the input method.
     * @see InputMethodInfo#computeId(ResolveInfo)
     */
    @Nullable
    public static ComponentName convertIdToComponentName(@NonNull String id) {
        return ComponentName.unflattenFromString(id);
    }
}