Patrick Baumann | 6c1c809 | 2019-06-27 14:55:44 -0700 | [diff] [blame^] | 1 | /* |
| 2 | * Copyright (C) 2019 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package com.android.server.pm; |
| 18 | |
| 19 | import android.Manifest; |
| 20 | import android.annotation.Nullable; |
| 21 | import android.app.AppOpsManager; |
| 22 | import android.content.Context; |
| 23 | import android.content.Intent; |
| 24 | import android.content.pm.PackageManager; |
| 25 | import android.content.pm.PackageParser; |
| 26 | import android.os.Build; |
| 27 | import android.os.Process; |
| 28 | import android.os.RemoteException; |
| 29 | import android.os.ServiceManager; |
| 30 | import android.permission.IPermissionManager; |
| 31 | import android.provider.DeviceConfig; |
| 32 | import android.util.ArraySet; |
| 33 | import android.util.Slog; |
| 34 | import android.util.SparseArray; |
| 35 | |
| 36 | import com.android.internal.R; |
| 37 | |
| 38 | import java.util.ArrayList; |
| 39 | import java.util.Collections; |
| 40 | import java.util.HashMap; |
| 41 | import java.util.HashSet; |
| 42 | import java.util.List; |
| 43 | import java.util.Map; |
| 44 | import java.util.Set; |
| 45 | |
| 46 | /** |
| 47 | * The entity responsible for filtering visibility between apps based on declarations in their |
| 48 | * manifests. |
| 49 | */ |
| 50 | class AppsFilter { |
| 51 | |
| 52 | private static final String TAG = PackageManagerService.TAG; |
| 53 | /** |
| 54 | * This contains a list of packages that are implicitly queryable because another app explicitly |
| 55 | * interacted with it. For example, if application A starts a service in application B, |
| 56 | * application B is implicitly allowed to query for application A; regardless of any manifest |
| 57 | * entries. |
| 58 | */ |
| 59 | private final SparseArray<HashMap<String, ArrayList<String>>> mImplicitlyQueryable = |
| 60 | new SparseArray<>(); |
| 61 | |
| 62 | /** |
| 63 | * A mapping from the set of packages that query other packages via package name to the |
| 64 | * list of packages that they can see. |
| 65 | */ |
| 66 | private final HashMap<String, List<String>> mQueriesViaPackage = new HashMap<>(); |
| 67 | |
| 68 | /** |
| 69 | * A mapping from the set of packages that query others via intent to the list |
| 70 | * of packages that the intents resolve to. |
| 71 | */ |
| 72 | private final HashMap<String, List<String>> mQueriesViaIntent = new HashMap<>(); |
| 73 | |
| 74 | /** |
| 75 | * A set of packages that are always queryable by any package, regardless of their manifest |
| 76 | * content. |
| 77 | */ |
| 78 | private final HashSet<String> mForceQueryable; |
| 79 | /** |
| 80 | * A set of packages that are always queryable by any package, regardless of their manifest |
| 81 | * content. |
| 82 | */ |
| 83 | private final Set<String> mForceQueryableByDevice; |
| 84 | |
| 85 | /** True if all system apps should be made queryable by default. */ |
| 86 | private final boolean mSystemAppsQueryable; |
| 87 | |
| 88 | private final IPermissionManager mPermissionManager; |
| 89 | |
| 90 | private final AppOpsManager mAppOpsManager; |
| 91 | private final ConfigProvider mConfigProvider; |
| 92 | |
| 93 | AppsFilter(ConfigProvider configProvider, IPermissionManager permissionManager, |
| 94 | AppOpsManager appOpsManager, String[] forceQueryableWhitelist, |
| 95 | boolean systemAppsQueryable) { |
| 96 | mConfigProvider = configProvider; |
| 97 | mAppOpsManager = appOpsManager; |
| 98 | final HashSet<String> forceQueryableByDeviceSet = new HashSet<>(); |
| 99 | Collections.addAll(forceQueryableByDeviceSet, forceQueryableWhitelist); |
| 100 | this.mForceQueryableByDevice = Collections.unmodifiableSet(forceQueryableByDeviceSet); |
| 101 | this.mForceQueryable = new HashSet<>(); |
| 102 | mPermissionManager = permissionManager; |
| 103 | mSystemAppsQueryable = systemAppsQueryable; |
| 104 | } |
| 105 | |
| 106 | public static AppsFilter create(Context context) { |
| 107 | final boolean forceSystemAppsQueryable = |
| 108 | context.getResources().getBoolean(R.bool.config_forceSystemPackagesQueryable); |
| 109 | final ConfigProvider configProvider = () -> DeviceConfig.getBoolean( |
| 110 | DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE, |
| 111 | "package_query_filtering_enabled", |
| 112 | false); |
| 113 | final String[] forcedQueryablePackageNames; |
| 114 | if (forceSystemAppsQueryable) { |
| 115 | // all system apps already queryable, no need to read and parse individual exceptions |
| 116 | forcedQueryablePackageNames = new String[]{}; |
| 117 | } else { |
| 118 | forcedQueryablePackageNames = |
| 119 | context.getResources().getStringArray(R.array.config_forceQueryablePackages); |
| 120 | for (int i = 0; i < forcedQueryablePackageNames.length; i++) { |
| 121 | forcedQueryablePackageNames[i] = forcedQueryablePackageNames[i].intern(); |
| 122 | } |
| 123 | } |
| 124 | IPermissionManager permissionmgr = |
| 125 | (IPermissionManager) ServiceManager.getService("permissionmgr"); |
| 126 | return new AppsFilter(configProvider, permissionmgr, |
| 127 | context.getSystemService(AppOpsManager.class), forcedQueryablePackageNames, |
| 128 | forceSystemAppsQueryable); |
| 129 | } |
| 130 | |
| 131 | /** Returns true if the querying package may query for the potential target package */ |
| 132 | private static boolean canQuery(PackageParser.Package querying, |
| 133 | PackageParser.Package potentialTarget) { |
| 134 | if (querying.mQueriesIntents == null) { |
| 135 | return false; |
| 136 | } |
| 137 | for (Intent intent : querying.mQueriesIntents) { |
| 138 | for (PackageParser.Activity activity : potentialTarget.activities) { |
| 139 | if (activity.intents != null) { |
| 140 | for (PackageParser.ActivityIntentInfo filter : activity.intents) { |
| 141 | if (matches(intent, filter)) { |
| 142 | return true; |
| 143 | } |
| 144 | } |
| 145 | } |
| 146 | } |
| 147 | } |
| 148 | return false; |
| 149 | } |
| 150 | |
| 151 | /** Returns true if the given intent matches the given filter. */ |
| 152 | private static boolean matches(Intent intent, PackageParser.ActivityIntentInfo filter) { |
| 153 | return filter.match(intent.getAction(), intent.getType(), intent.getScheme(), |
| 154 | intent.getData(), intent.getCategories(), "AppsFilter") > 0; |
| 155 | } |
| 156 | |
| 157 | /** |
| 158 | * Marks that a package initiated an interaction with another package, granting visibility of |
| 159 | * the prior from the former. |
| 160 | * |
| 161 | * @param initiatingPackage the package initiating the interaction |
| 162 | * @param targetPackage the package being interacted with and thus gaining visibility of the |
| 163 | * initiating package. |
| 164 | * @param userId the user in which this interaction was taking place |
| 165 | */ |
| 166 | private void markAppInteraction( |
| 167 | PackageSetting initiatingPackage, PackageSetting targetPackage, int userId) { |
| 168 | HashMap<String, ArrayList<String>> currentUser = mImplicitlyQueryable.get(userId); |
| 169 | if (currentUser == null) { |
| 170 | currentUser = new HashMap<>(); |
| 171 | mImplicitlyQueryable.put(userId, currentUser); |
| 172 | } |
| 173 | if (!currentUser.containsKey(targetPackage.pkg.packageName)) { |
| 174 | currentUser.put(targetPackage.pkg.packageName, new ArrayList<>()); |
| 175 | } |
| 176 | currentUser.get(targetPackage.pkg.packageName).add(initiatingPackage.pkg.packageName); |
| 177 | } |
| 178 | |
| 179 | /** |
| 180 | * Adds a package that should be considered when filtering visibility between apps. |
| 181 | * |
| 182 | * @param newPkg the new package being added |
| 183 | * @param existing all other packages currently on the device. |
| 184 | */ |
| 185 | public void addPackage(PackageParser.Package newPkg, |
| 186 | Map<String, PackageParser.Package> existing) { |
| 187 | // let's re-evaluate the ability of already added packages to see this new package |
| 188 | if (newPkg.mForceQueryable |
| 189 | || (mSystemAppsQueryable && (newPkg.isSystem() || newPkg.isUpdatedSystemApp()))) { |
| 190 | mForceQueryable.add(newPkg.packageName); |
| 191 | } else { |
| 192 | for (String packageName : mQueriesViaIntent.keySet()) { |
| 193 | if (packageName == newPkg.packageName) { |
| 194 | continue; |
| 195 | } |
| 196 | final PackageParser.Package existingPackage = existing.get(packageName); |
| 197 | if (canQuery(existingPackage, newPkg)) { |
| 198 | mQueriesViaIntent.get(packageName).add(newPkg.packageName); |
| 199 | } |
| 200 | } |
| 201 | } |
| 202 | // if the new package declares them, let's evaluate its ability to see existing packages |
| 203 | mQueriesViaIntent.put(newPkg.packageName, new ArrayList<>()); |
| 204 | for (PackageParser.Package existingPackage : existing.values()) { |
| 205 | if (existingPackage.packageName == newPkg.packageName) { |
| 206 | continue; |
| 207 | } |
| 208 | if (existingPackage.mForceQueryable |
| 209 | || (mSystemAppsQueryable |
| 210 | && (newPkg.isSystem() || newPkg.isUpdatedSystemApp()))) { |
| 211 | continue; |
| 212 | } |
| 213 | if (canQuery(newPkg, existingPackage)) { |
| 214 | mQueriesViaIntent.get(newPkg.packageName).add(existingPackage.packageName); |
| 215 | } |
| 216 | } |
| 217 | final ArrayList<String> queriesPackages = new ArrayList<>( |
| 218 | newPkg.mQueriesPackages == null ? 0 : newPkg.mQueriesPackages.size()); |
| 219 | if (newPkg.mQueriesPackages != null) { |
| 220 | queriesPackages.addAll(newPkg.mQueriesPackages); |
| 221 | } |
| 222 | mQueriesViaPackage.put(newPkg.packageName, queriesPackages); |
| 223 | } |
| 224 | |
| 225 | /** |
| 226 | * Removes a package for consideration when filtering visibility between apps. |
| 227 | * |
| 228 | * @param packageName the name of the package being removed. |
| 229 | */ |
| 230 | public void removePackage(String packageName) { |
| 231 | mForceQueryable.remove(packageName); |
| 232 | |
| 233 | for (int i = 0; i < mImplicitlyQueryable.size(); i++) { |
| 234 | mImplicitlyQueryable.valueAt(i).remove(packageName); |
| 235 | for (ArrayList<String> initiators : mImplicitlyQueryable.valueAt(i).values()) { |
| 236 | initiators.remove(packageName); |
| 237 | } |
| 238 | } |
| 239 | |
| 240 | mQueriesViaIntent.remove(packageName); |
| 241 | for (List<String> declarators : mQueriesViaIntent.values()) { |
| 242 | declarators.remove(packageName); |
| 243 | } |
| 244 | |
| 245 | mQueriesViaPackage.remove(packageName); |
| 246 | } |
| 247 | |
| 248 | /** |
| 249 | * Returns true if the calling package should not be able to see the target package, false if no |
| 250 | * filtering should be done. |
| 251 | * |
| 252 | * @param callingUid the uid of the caller attempting to access a package |
| 253 | * @param callingSetting the setting attempting to access a package or null if it could not be |
| 254 | * found |
| 255 | * @param targetPkgSetting the package being accessed |
| 256 | * @param userId the user in which this access is being attempted |
| 257 | */ |
| 258 | public boolean shouldFilterApplication(int callingUid, @Nullable SettingBase callingSetting, |
| 259 | PackageSetting targetPkgSetting, int userId) { |
| 260 | if (callingUid < Process.FIRST_APPLICATION_UID) { |
| 261 | return false; |
| 262 | } |
| 263 | if (callingSetting == null) { |
| 264 | Slog.wtf(TAG, "No setting found for non system uid " + callingUid); |
| 265 | return true; |
| 266 | } |
| 267 | PackageSetting callingPkgSetting = null; |
| 268 | if (callingSetting instanceof PackageSetting) { |
| 269 | callingPkgSetting = (PackageSetting) callingSetting; |
| 270 | if (!shouldFilterApplicationInternal(callingPkgSetting, targetPkgSetting, |
| 271 | userId)) { |
| 272 | // TODO: actually base this on a start / launch (not just a query) |
| 273 | markAppInteraction(callingPkgSetting, targetPkgSetting, userId); |
| 274 | return false; |
| 275 | } |
| 276 | } else if (callingSetting instanceof SharedUserSetting) { |
| 277 | final ArraySet<PackageSetting> packageSettings = |
| 278 | ((SharedUserSetting) callingSetting).packages; |
| 279 | if (packageSettings != null && packageSettings.size() > 0) { |
| 280 | for (PackageSetting packageSetting : packageSettings) { |
| 281 | if (!shouldFilterApplicationInternal(packageSetting, targetPkgSetting, |
| 282 | userId)) { |
| 283 | // TODO: actually base this on a start / launch (not just a query) |
| 284 | markAppInteraction(packageSetting, targetPkgSetting, userId); |
| 285 | return false; |
| 286 | } |
| 287 | if (callingPkgSetting == null && packageSetting.pkg != null) { |
| 288 | callingPkgSetting = packageSetting; |
| 289 | } |
| 290 | } |
| 291 | } else { |
| 292 | return true; |
| 293 | } |
| 294 | } |
| 295 | if (callingPkgSetting == null) { |
| 296 | Slog.wtf(TAG, "What... " + callingSetting); |
| 297 | return true; |
| 298 | } |
| 299 | final int mode = mAppOpsManager |
| 300 | .checkOpNoThrow(AppOpsManager.OP_QUERY_ALL_PACKAGES, callingUid, |
| 301 | callingPkgSetting.pkg.packageName); |
| 302 | switch (mode) { |
| 303 | case AppOpsManager.MODE_DEFAULT: |
| 304 | // if default, let's rely on remote feature toggle to determine whether to |
| 305 | // actually filter |
| 306 | return mConfigProvider.isEnabled(); |
| 307 | case AppOpsManager.MODE_ALLOWED: |
| 308 | // explicitly allowed to see all packages, don't filter |
| 309 | return false; |
| 310 | case AppOpsManager.MODE_ERRORED: |
| 311 | // deny / error: let's log so developer can audit usages |
| 312 | Slog.i(TAG, callingPkgSetting.pkg.packageName |
| 313 | + " blocked from accessing " + targetPkgSetting.pkg.packageName); |
| 314 | case AppOpsManager.MODE_IGNORED: |
| 315 | // fall through |
| 316 | default: |
| 317 | return true; |
| 318 | } |
| 319 | } |
| 320 | |
| 321 | private boolean shouldFilterApplicationInternal( |
| 322 | PackageSetting callingPkgSetting, PackageSetting targetPkgSetting, int userId) { |
| 323 | final String callingName = callingPkgSetting.pkg.packageName; |
| 324 | final String targetName = targetPkgSetting.pkg.packageName; |
| 325 | if (callingPkgSetting.pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.R) { |
| 326 | return false; |
| 327 | } |
| 328 | if (isImplicitlyQueryableSystemApp(targetPkgSetting)) { |
| 329 | return false; |
| 330 | } |
| 331 | if (targetPkgSetting.pkg.mForceQueryable) { |
| 332 | return false; |
| 333 | } |
| 334 | if (mForceQueryable.contains(targetName)) { |
| 335 | return false; |
| 336 | } |
| 337 | if (mQueriesViaPackage.containsKey(callingName) |
| 338 | && mQueriesViaPackage.get(callingName).contains( |
| 339 | targetName)) { |
| 340 | // the calling package has explicitly declared the target package; allow |
| 341 | return false; |
| 342 | } else if (mQueriesViaIntent.containsKey(callingName) |
| 343 | && mQueriesViaIntent.get(callingName).contains(targetName)) { |
| 344 | return false; |
| 345 | } |
| 346 | if (mImplicitlyQueryable.get(userId) != null |
| 347 | && mImplicitlyQueryable.get(userId).containsKey(callingName) |
| 348 | && mImplicitlyQueryable.get(userId).get(callingName).contains(targetName)) { |
| 349 | return false; |
| 350 | } |
| 351 | try { |
| 352 | if (mPermissionManager.checkPermission( |
| 353 | Manifest.permission.QUERY_ALL_PACKAGES, callingName, userId) |
| 354 | == PackageManager.PERMISSION_GRANTED) { |
| 355 | return false; |
| 356 | } |
| 357 | } catch (RemoteException e) { |
| 358 | return true; |
| 359 | } |
| 360 | return true; |
| 361 | } |
| 362 | |
| 363 | private boolean isImplicitlyQueryableSystemApp(PackageSetting targetPkgSetting) { |
| 364 | return targetPkgSetting.isSystem() && (mSystemAppsQueryable |
| 365 | || mForceQueryableByDevice.contains(targetPkgSetting.pkg.packageName)); |
| 366 | } |
| 367 | |
| 368 | public interface ConfigProvider { |
| 369 | boolean isEnabled(); |
| 370 | } |
| 371 | |
| 372 | } |