1 /* 2 * Copyright (C) 2022 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 static android.os.Process.THREAD_PRIORITY_DEFAULT; 20 21 import android.Manifest; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.content.Intent; 25 import android.content.IntentFilter; 26 import android.util.ArrayMap; 27 import android.util.ArraySet; 28 import android.util.Pair; 29 import android.util.SparseSetArray; 30 31 import com.android.internal.pm.pkg.component.ParsedComponent; 32 import com.android.internal.pm.pkg.component.ParsedIntentInfo; 33 import com.android.internal.pm.pkg.component.ParsedMainComponent; 34 import com.android.internal.pm.pkg.component.ParsedProvider; 35 import com.android.internal.util.ArrayUtils; 36 import com.android.internal.util.ConcurrentUtils; 37 import com.android.server.pm.pkg.AndroidPackage; 38 import com.android.server.pm.pkg.PackageState; 39 import com.android.server.pm.pkg.PackageStateInternal; 40 import com.android.server.utils.WatchedArraySet; 41 42 import java.util.ArrayList; 43 import java.util.List; 44 import java.util.Set; 45 import java.util.StringTokenizer; 46 import java.util.concurrent.ExecutionException; 47 import java.util.concurrent.ExecutorService; 48 import java.util.concurrent.Future; 49 50 final class AppsFilterUtils { requestsQueryAllPackages(@onNull AndroidPackage pkg)51 public static boolean requestsQueryAllPackages(@NonNull AndroidPackage pkg) { 52 // we're not guaranteed to have permissions yet analyzed at package add, so we inspect the 53 // package directly 54 return pkg.getRequestedPermissions().contains( 55 Manifest.permission.QUERY_ALL_PACKAGES); 56 } 57 58 /** Returns true if the querying package may query for the potential target package */ canQueryViaComponents(AndroidPackage querying, AndroidPackage potentialTarget, WatchedArraySet<String> protectedBroadcasts)59 public static boolean canQueryViaComponents(AndroidPackage querying, 60 AndroidPackage potentialTarget, WatchedArraySet<String> protectedBroadcasts) { 61 if (!querying.getQueriesIntents().isEmpty()) { 62 for (Intent intent : querying.getQueriesIntents()) { 63 if (matchesPackage(intent, potentialTarget, protectedBroadcasts)) { 64 return true; 65 } 66 } 67 } 68 if (!querying.getQueriesProviders().isEmpty() 69 && matchesProviders(querying.getQueriesProviders(), potentialTarget)) { 70 return true; 71 } 72 return false; 73 } 74 canQueryViaPackage(AndroidPackage querying, AndroidPackage potentialTarget)75 public static boolean canQueryViaPackage(AndroidPackage querying, 76 AndroidPackage potentialTarget) { 77 return !querying.getQueriesPackages().isEmpty() 78 && querying.getQueriesPackages().contains(potentialTarget.getPackageName()); 79 } 80 canQueryAsInstaller(PackageStateInternal querying, AndroidPackage potentialTarget)81 public static boolean canQueryAsInstaller(PackageStateInternal querying, 82 AndroidPackage potentialTarget) { 83 final InstallSource installSource = querying.getInstallSource(); 84 if (potentialTarget.getPackageName().equals(installSource.mInstallerPackageName)) { 85 return true; 86 } 87 if (!installSource.mIsInitiatingPackageUninstalled 88 && potentialTarget.getPackageName().equals(installSource.mInitiatingPackageName)) { 89 return true; 90 } 91 return false; 92 } 93 canQueryAsUpdateOwner(PackageStateInternal querying, AndroidPackage potentialTarget)94 public static boolean canQueryAsUpdateOwner(PackageStateInternal querying, 95 AndroidPackage potentialTarget) { 96 final InstallSource installSource = querying.getInstallSource(); 97 if (potentialTarget.getPackageName().equals(installSource.mUpdateOwnerPackageName)) { 98 return true; 99 } 100 return false; 101 } 102 canQueryViaUsesLibrary(AndroidPackage querying, AndroidPackage potentialTarget)103 public static boolean canQueryViaUsesLibrary(AndroidPackage querying, 104 AndroidPackage potentialTarget) { 105 if (potentialTarget.getLibraryNames().isEmpty()) { 106 return false; 107 } 108 final List<String> libNames = potentialTarget.getLibraryNames(); 109 for (int i = 0, size = libNames.size(); i < size; i++) { 110 final String libName = libNames.get(i); 111 if (querying.getUsesLibraries().contains(libName) 112 || querying.getUsesOptionalLibraries().contains(libName)) { 113 return true; 114 } 115 } 116 return false; 117 } 118 matchesProviders( Set<String> queriesAuthorities, AndroidPackage potentialTarget)119 private static boolean matchesProviders( 120 Set<String> queriesAuthorities, AndroidPackage potentialTarget) { 121 for (int p = ArrayUtils.size(potentialTarget.getProviders()) - 1; p >= 0; p--) { 122 ParsedProvider provider = potentialTarget.getProviders().get(p); 123 if (!provider.isExported()) { 124 continue; 125 } 126 if (provider.getAuthority() == null) { 127 continue; 128 } 129 StringTokenizer authorities = new StringTokenizer(provider.getAuthority(), ";", 130 false); 131 while (authorities.hasMoreElements()) { 132 if (queriesAuthorities.contains(authorities.nextToken())) { 133 return true; 134 } 135 } 136 } 137 return false; 138 } 139 matchesPackage(Intent intent, AndroidPackage potentialTarget, WatchedArraySet<String> protectedBroadcasts)140 private static boolean matchesPackage(Intent intent, AndroidPackage potentialTarget, 141 WatchedArraySet<String> protectedBroadcasts) { 142 if (matchesAnyComponents( 143 intent, potentialTarget.getServices(), null /*protectedBroadcasts*/)) { 144 return true; 145 } 146 if (matchesAnyComponents( 147 intent, potentialTarget.getActivities(), null /*protectedBroadcasts*/)) { 148 return true; 149 } 150 if (matchesAnyComponents(intent, potentialTarget.getReceivers(), protectedBroadcasts)) { 151 return true; 152 } 153 if (matchesAnyComponents( 154 intent, potentialTarget.getProviders(), null /*protectedBroadcasts*/)) { 155 return true; 156 } 157 return false; 158 } 159 matchesAnyComponents(Intent intent, List<? extends ParsedMainComponent> components, WatchedArraySet<String> protectedBroadcasts)160 private static boolean matchesAnyComponents(Intent intent, 161 List<? extends ParsedMainComponent> components, 162 WatchedArraySet<String> protectedBroadcasts) { 163 for (int i = ArrayUtils.size(components) - 1; i >= 0; i--) { 164 ParsedMainComponent component = components.get(i); 165 if (!component.isExported()) { 166 continue; 167 } 168 if (matchesAnyFilter(intent, component, protectedBroadcasts)) { 169 return true; 170 } 171 } 172 return false; 173 } 174 matchesAnyFilter(Intent intent, ParsedComponent component, WatchedArraySet<String> protectedBroadcasts)175 private static boolean matchesAnyFilter(Intent intent, ParsedComponent component, 176 WatchedArraySet<String> protectedBroadcasts) { 177 List<ParsedIntentInfo> intents = component.getIntents(); 178 for (int i = ArrayUtils.size(intents) - 1; i >= 0; i--) { 179 IntentFilter intentFilter = intents.get(i).getIntentFilter(); 180 if (matchesIntentFilter(intent, intentFilter, protectedBroadcasts)) { 181 return true; 182 } 183 } 184 return false; 185 } 186 matchesIntentFilter(Intent intent, IntentFilter intentFilter, @Nullable WatchedArraySet<String> protectedBroadcasts)187 private static boolean matchesIntentFilter(Intent intent, IntentFilter intentFilter, 188 @Nullable WatchedArraySet<String> protectedBroadcasts) { 189 return intentFilter.match(intent.getAction(), intent.getType(), intent.getScheme(), 190 intent.getData(), intent.getCategories(), "AppsFilter", true, 191 protectedBroadcasts != null ? protectedBroadcasts.untrackedStorage() : null) > 0; 192 } 193 194 /** 195 * A helper class for parallel computing of component visibility of all packages on the device. 196 */ 197 public static final class ParallelComputeComponentVisibility { 198 private static final int MAX_THREADS = 4; 199 200 private final ArrayMap<String, ? extends PackageStateInternal> mExistingSettings; 201 private final ArraySet<Integer> mForceQueryable; 202 private final WatchedArraySet<String> mProtectedBroadcasts; 203 ParallelComputeComponentVisibility( @onNull ArrayMap<String, ? extends PackageStateInternal> existingSettings, @NonNull ArraySet<Integer> forceQueryable, @NonNull WatchedArraySet<String> protectedBroadcasts)204 ParallelComputeComponentVisibility( 205 @NonNull ArrayMap<String, ? extends PackageStateInternal> existingSettings, 206 @NonNull ArraySet<Integer> forceQueryable, 207 @NonNull WatchedArraySet<String> protectedBroadcasts) { 208 mExistingSettings = existingSettings; 209 mForceQueryable = forceQueryable; 210 mProtectedBroadcasts = protectedBroadcasts; 211 } 212 213 /** 214 * Computes component visibility of all packages in parallel from a thread pool. 215 */ 216 @NonNull execute()217 SparseSetArray<Integer> execute() { 218 final SparseSetArray<Integer> queriesViaComponent = new SparseSetArray<>(); 219 final ExecutorService pool = ConcurrentUtils.newFixedThreadPool( 220 MAX_THREADS, ParallelComputeComponentVisibility.class.getSimpleName(), 221 THREAD_PRIORITY_DEFAULT); 222 try { 223 final List<Pair<PackageState, Future<ArraySet<Integer>>>> futures = 224 new ArrayList<>(); 225 for (int i = mExistingSettings.size() - 1; i >= 0; i--) { 226 final PackageStateInternal setting = mExistingSettings.valueAt(i); 227 final AndroidPackage pkg = setting.getPkg(); 228 if (pkg == null || requestsQueryAllPackages(pkg)) { 229 continue; 230 } 231 if (pkg.getQueriesIntents().isEmpty() 232 && pkg.getQueriesProviders().isEmpty()) { 233 continue; 234 } 235 futures.add(new Pair(setting, 236 pool.submit(() -> getVisibleListOfQueryViaComponents(setting)))); 237 } 238 for (int i = 0; i < futures.size(); i++) { 239 final int appId = futures.get(i).first.getAppId(); 240 final Future<ArraySet<Integer>> future = futures.get(i).second; 241 try { 242 final ArraySet<Integer> visibleList = future.get(); 243 if (visibleList.size() != 0) { 244 queriesViaComponent.addAll(appId, visibleList); 245 } 246 } catch (InterruptedException | ExecutionException e) { 247 throw new IllegalStateException(e); 248 } 249 } 250 } finally { 251 pool.shutdownNow(); 252 } 253 return queriesViaComponent; 254 } 255 256 /** 257 * Returns a set of app IDs that contains components resolved by the queries intent 258 * or provider that declared in the manifest of the querying package. 259 * 260 * @param setting The package to query. 261 * @return A set of app IDs. 262 */ 263 @NonNull getVisibleListOfQueryViaComponents( @onNull PackageStateInternal setting)264 private ArraySet<Integer> getVisibleListOfQueryViaComponents( 265 @NonNull PackageStateInternal setting) { 266 final ArraySet<Integer> result = new ArraySet(); 267 for (int i = mExistingSettings.size() - 1; i >= 0; i--) { 268 final PackageStateInternal otherSetting = mExistingSettings.valueAt(i); 269 if (setting.getAppId() == otherSetting.getAppId()) { 270 continue; 271 } 272 if (otherSetting.getPkg() == null || mForceQueryable.contains( 273 otherSetting.getAppId())) { 274 continue; 275 } 276 final boolean canQuery = canQueryViaComponents( 277 setting.getPkg(), otherSetting.getPkg(), mProtectedBroadcasts); 278 if (canQuery) { 279 result.add(otherSetting.getAppId()); 280 } 281 } 282 return result; 283 } 284 } 285 } 286