From 28f265138c01bbd296a3bb3124290c373775bf59 Mon Sep 17 00:00:00 2001
From: Rhed Jao
Date: Tue, 6 Sep 2022 14:55:49 +0800
Subject: [PATCH] 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
---
.../view/inputmethod/InputMethodManager.java | 6 ++
.../InputMethodManagerService.java | 84 +++++++++++++++----
.../server/inputmethod/InputMethodUtils.java | 14 ++++
3 files changed, 88 insertions(+), 16 deletions(-)
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index c1fe6866fb4d5..2ff4577bf148a 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -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.
*
+ *
+ * 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
+ * package visibility,
+ * except for the currently connected IME. Apps having a query for the
+ * {@link InputMethod#SERVICE_INTERFACE} see all IMEs.
*/
@SystemService(Context.INPUT_METHOD_SERVICE)
@RequiresFeature(PackageManager.FEATURE_INPUT_METHODS)
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index d479f573e22fc..d391e3939dd99 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -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 getInputMethodListLocked(@UserIdInt int userId,
- @DirectBootAwareness int directBootAwareness) {
+ @DirectBootAwareness int directBootAwareness, int callingUid) {
final ArrayList methodList;
+ final InputMethodSettings settings;
if (userId == mSettings.getCurrentUserId()
&& directBootAwareness == DirectBootAwareness.AUTO) {
// Create a copy.
methodList = new ArrayList<>(mMethodList);
+ settings = mSettings;
} else {
final ArrayMap 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 getEnabledInputMethodListLocked(@UserIdInt int userId) {
+ private List getEnabledInputMethodListLocked(@UserIdInt int userId,
+ int callingUid) {
+ final ArrayList methodList;
+ final InputMethodSettings settings;
if (userId == mSettings.getCurrentUserId()) {
- return mSettings.getEnabledInputMethodListLocked();
+ methodList = mSettings.getEnabledInputMethodListLocked();
+ settings = mSettings;
+ } else {
+ final ArrayMap methodMap = queryMethodMapForUser(userId);
+ settings = new InputMethodSettings(mContext, methodMap, userId, true /* copyOnWrite */);
+ methodList = settings.getEnabledInputMethodListLocked();
}
- final ArrayMap methodMap = queryMethodMapForUser(userId);
- final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap, userId,
- true);
- return 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 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 getInputMethodListAsUser(@UserIdInt int userId) {
synchronized (ImfLock.class) {
- return getInputMethodListLocked(userId, DirectBootAwareness.AUTO);
+ return getInputMethodListLocked(userId, DirectBootAwareness.AUTO,
+ Process.SYSTEM_UID);
}
}
@Override
public List 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 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);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index 69b06613b298e..5a9e5d2537c82 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -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);
+ }
}