blob: ab8cc5374ec93037b55b6dc32b1f728cd12f9369 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.pm;
import android.Manifest;
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
import android.os.Build;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.permission.IPermissionManager;
import android.provider.DeviceConfig;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.R;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* The entity responsible for filtering visibility between apps based on declarations in their
* manifests.
*/
class AppsFilter {
private static final String TAG = PackageManagerService.TAG;
/**
* This contains a list of packages that are implicitly queryable because another app explicitly
* interacted with it. For example, if application A starts a service in application B,
* application B is implicitly allowed to query for application A; regardless of any manifest
* entries.
*/
private final SparseArray<HashMap<String, ArrayList<String>>> mImplicitlyQueryable =
new SparseArray<>();
/**
* A mapping from the set of packages that query other packages via package name to the
* list of packages that they can see.
*/
private final HashMap<String, List<String>> mQueriesViaPackage = new HashMap<>();
/**
* A mapping from the set of packages that query others via intent to the list
* of packages that the intents resolve to.
*/
private final HashMap<String, List<String>> mQueriesViaIntent = new HashMap<>();
/**
* A set of packages that are always queryable by any package, regardless of their manifest
* content.
*/
private final HashSet<String> mForceQueryable;
/**
* A set of packages that are always queryable by any package, regardless of their manifest
* content.
*/
private final Set<String> mForceQueryableByDevice;
/** True if all system apps should be made queryable by default. */
private final boolean mSystemAppsQueryable;
private final IPermissionManager mPermissionManager;
private final AppOpsManager mAppOpsManager;
private final ConfigProvider mConfigProvider;
AppsFilter(ConfigProvider configProvider, IPermissionManager permissionManager,
AppOpsManager appOpsManager, String[] forceQueryableWhitelist,
boolean systemAppsQueryable) {
mConfigProvider = configProvider;
mAppOpsManager = appOpsManager;
final HashSet<String> forceQueryableByDeviceSet = new HashSet<>();
Collections.addAll(forceQueryableByDeviceSet, forceQueryableWhitelist);
this.mForceQueryableByDevice = Collections.unmodifiableSet(forceQueryableByDeviceSet);
this.mForceQueryable = new HashSet<>();
mPermissionManager = permissionManager;
mSystemAppsQueryable = systemAppsQueryable;
}
public static AppsFilter create(Context context) {
final boolean forceSystemAppsQueryable =
context.getResources().getBoolean(R.bool.config_forceSystemPackagesQueryable);
final ConfigProvider configProvider = () -> DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
"package_query_filtering_enabled",
false);
final String[] forcedQueryablePackageNames;
if (forceSystemAppsQueryable) {
// all system apps already queryable, no need to read and parse individual exceptions
forcedQueryablePackageNames = new String[]{};
} else {
forcedQueryablePackageNames =
context.getResources().getStringArray(R.array.config_forceQueryablePackages);
for (int i = 0; i < forcedQueryablePackageNames.length; i++) {
forcedQueryablePackageNames[i] = forcedQueryablePackageNames[i].intern();
}
}
IPermissionManager permissionmgr =
(IPermissionManager) ServiceManager.getService("permissionmgr");
return new AppsFilter(configProvider, permissionmgr,
context.getSystemService(AppOpsManager.class), forcedQueryablePackageNames,
forceSystemAppsQueryable);
}
/** Returns true if the querying package may query for the potential target package */
private static boolean canQuery(PackageParser.Package querying,
PackageParser.Package potentialTarget) {
if (querying.mQueriesIntents == null) {
return false;
}
for (Intent intent : querying.mQueriesIntents) {
for (PackageParser.Activity activity : potentialTarget.activities) {
if (activity.intents != null) {
for (PackageParser.ActivityIntentInfo filter : activity.intents) {
if (matches(intent, filter)) {
return true;
}
}
}
}
}
return false;
}
/** Returns true if the given intent matches the given filter. */
private static boolean matches(Intent intent, PackageParser.ActivityIntentInfo filter) {
return filter.match(intent.getAction(), intent.getType(), intent.getScheme(),
intent.getData(), intent.getCategories(), "AppsFilter") > 0;
}
/**
* Marks that a package initiated an interaction with another package, granting visibility of
* the prior from the former.
*
* @param initiatingPackage the package initiating the interaction
* @param targetPackage the package being interacted with and thus gaining visibility of the
* initiating package.
* @param userId the user in which this interaction was taking place
*/
private void markAppInteraction(
PackageSetting initiatingPackage, PackageSetting targetPackage, int userId) {
HashMap<String, ArrayList<String>> currentUser = mImplicitlyQueryable.get(userId);
if (currentUser == null) {
currentUser = new HashMap<>();
mImplicitlyQueryable.put(userId, currentUser);
}
if (!currentUser.containsKey(targetPackage.pkg.packageName)) {
currentUser.put(targetPackage.pkg.packageName, new ArrayList<>());
}
currentUser.get(targetPackage.pkg.packageName).add(initiatingPackage.pkg.packageName);
}
/**
* Adds a package that should be considered when filtering visibility between apps.
*
* @param newPkg the new package being added
* @param existing all other packages currently on the device.
*/
public void addPackage(PackageParser.Package newPkg,
Map<String, PackageParser.Package> existing) {
// let's re-evaluate the ability of already added packages to see this new package
if (newPkg.mForceQueryable
|| (mSystemAppsQueryable && (newPkg.isSystem() || newPkg.isUpdatedSystemApp()))) {
mForceQueryable.add(newPkg.packageName);
} else {
for (String packageName : mQueriesViaIntent.keySet()) {
if (packageName == newPkg.packageName) {
continue;
}
final PackageParser.Package existingPackage = existing.get(packageName);
if (canQuery(existingPackage, newPkg)) {
mQueriesViaIntent.get(packageName).add(newPkg.packageName);
}
}
}
// if the new package declares them, let's evaluate its ability to see existing packages
mQueriesViaIntent.put(newPkg.packageName, new ArrayList<>());
for (PackageParser.Package existingPackage : existing.values()) {
if (existingPackage.packageName == newPkg.packageName) {
continue;
}
if (existingPackage.mForceQueryable
|| (mSystemAppsQueryable
&& (newPkg.isSystem() || newPkg.isUpdatedSystemApp()))) {
continue;
}
if (canQuery(newPkg, existingPackage)) {
mQueriesViaIntent.get(newPkg.packageName).add(existingPackage.packageName);
}
}
final ArrayList<String> queriesPackages = new ArrayList<>(
newPkg.mQueriesPackages == null ? 0 : newPkg.mQueriesPackages.size());
if (newPkg.mQueriesPackages != null) {
queriesPackages.addAll(newPkg.mQueriesPackages);
}
mQueriesViaPackage.put(newPkg.packageName, queriesPackages);
}
/**
* Removes a package for consideration when filtering visibility between apps.
*
* @param packageName the name of the package being removed.
*/
public void removePackage(String packageName) {
mForceQueryable.remove(packageName);
for (int i = 0; i < mImplicitlyQueryable.size(); i++) {
mImplicitlyQueryable.valueAt(i).remove(packageName);
for (ArrayList<String> initiators : mImplicitlyQueryable.valueAt(i).values()) {
initiators.remove(packageName);
}
}
mQueriesViaIntent.remove(packageName);
for (List<String> declarators : mQueriesViaIntent.values()) {
declarators.remove(packageName);
}
mQueriesViaPackage.remove(packageName);
}
/**
* Returns true if the calling package should not be able to see the target package, false if no
* filtering should be done.
*
* @param callingUid the uid of the caller attempting to access a package
* @param callingSetting the setting attempting to access a package or null if it could not be
* found
* @param targetPkgSetting the package being accessed
* @param userId the user in which this access is being attempted
*/
public boolean shouldFilterApplication(int callingUid, @Nullable SettingBase callingSetting,
PackageSetting targetPkgSetting, int userId) {
if (callingUid < Process.FIRST_APPLICATION_UID) {
return false;
}
if (callingSetting == null) {
Slog.wtf(TAG, "No setting found for non system uid " + callingUid);
return true;
}
PackageSetting callingPkgSetting = null;
if (callingSetting instanceof PackageSetting) {
callingPkgSetting = (PackageSetting) callingSetting;
if (!shouldFilterApplicationInternal(callingPkgSetting, targetPkgSetting,
userId)) {
// TODO: actually base this on a start / launch (not just a query)
markAppInteraction(callingPkgSetting, targetPkgSetting, userId);
return false;
}
} else if (callingSetting instanceof SharedUserSetting) {
final ArraySet<PackageSetting> packageSettings =
((SharedUserSetting) callingSetting).packages;
if (packageSettings != null && packageSettings.size() > 0) {
for (PackageSetting packageSetting : packageSettings) {
if (!shouldFilterApplicationInternal(packageSetting, targetPkgSetting,
userId)) {
// TODO: actually base this on a start / launch (not just a query)
markAppInteraction(packageSetting, targetPkgSetting, userId);
return false;
}
if (callingPkgSetting == null && packageSetting.pkg != null) {
callingPkgSetting = packageSetting;
}
}
} else {
return true;
}
}
if (callingPkgSetting == null) {
Slog.wtf(TAG, "What... " + callingSetting);
return true;
}
final int mode = mAppOpsManager
.checkOpNoThrow(AppOpsManager.OP_QUERY_ALL_PACKAGES, callingUid,
callingPkgSetting.pkg.packageName);
switch (mode) {
case AppOpsManager.MODE_DEFAULT:
// if default, let's rely on remote feature toggle to determine whether to
// actually filter
return mConfigProvider.isEnabled();
case AppOpsManager.MODE_ALLOWED:
// explicitly allowed to see all packages, don't filter
return false;
case AppOpsManager.MODE_ERRORED:
// deny / error: let's log so developer can audit usages
Slog.i(TAG, callingPkgSetting.pkg.packageName
+ " blocked from accessing " + targetPkgSetting.pkg.packageName);
case AppOpsManager.MODE_IGNORED:
// fall through
default:
return true;
}
}
private boolean shouldFilterApplicationInternal(
PackageSetting callingPkgSetting, PackageSetting targetPkgSetting, int userId) {
final String callingName = callingPkgSetting.pkg.packageName;
final String targetName = targetPkgSetting.pkg.packageName;
if (callingPkgSetting.pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.R) {
return false;
}
if (isImplicitlyQueryableSystemApp(targetPkgSetting)) {
return false;
}
if (targetPkgSetting.pkg.mForceQueryable) {
return false;
}
if (mForceQueryable.contains(targetName)) {
return false;
}
if (mQueriesViaPackage.containsKey(callingName)
&& mQueriesViaPackage.get(callingName).contains(
targetName)) {
// the calling package has explicitly declared the target package; allow
return false;
} else if (mQueriesViaIntent.containsKey(callingName)
&& mQueriesViaIntent.get(callingName).contains(targetName)) {
return false;
}
if (mImplicitlyQueryable.get(userId) != null
&& mImplicitlyQueryable.get(userId).containsKey(callingName)
&& mImplicitlyQueryable.get(userId).get(callingName).contains(targetName)) {
return false;
}
try {
if (mPermissionManager.checkPermission(
Manifest.permission.QUERY_ALL_PACKAGES, callingName, userId)
== PackageManager.PERMISSION_GRANTED) {
return false;
}
} catch (RemoteException e) {
return true;
}
return true;
}
private boolean isImplicitlyQueryableSystemApp(PackageSetting targetPkgSetting) {
return targetPkgSetting.isSystem() && (mSystemAppsQueryable
|| mForceQueryableByDevice.contains(targetPkgSetting.pkg.packageName));
}
public interface ConfigProvider {
boolean isEnabled();
}
}