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
This commit is contained in:
Rhed Jao
2022-09-06 14:55:49 +08:00
parent a918fd1767
commit 28f265138c
3 changed files with 88 additions and 16 deletions

View File

@@ -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)

View File

@@ -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);
settings = new InputMethodSettings(mContext, methodMap, userId, true /* copyOnWrite */);
methodList = settings.getEnabledInputMethodListLocked();
}
final ArrayMap<String, InputMethodInfo> 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<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);

View File

@@ -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);
}
}