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 30 import com.android.internal.util.ArrayUtils; 31 import com.android.internal.util.ConcurrentUtils; 32 import com.android.server.pm.pkg.AndroidPackage; 33 import com.android.server.pm.pkg.PackageState; 34 import com.android.server.pm.pkg.PackageStateInternal; 35 import com.android.server.pm.pkg.component.ParsedComponent; 36 import com.android.server.pm.pkg.component.ParsedIntentInfo; 37 import com.android.server.pm.pkg.component.ParsedMainComponent; 38 import com.android.server.pm.pkg.component.ParsedProvider; 39 import com.android.server.utils.WatchedArraySet; 40 import com.android.server.utils.WatchedSparseSetArray; 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 WatchedArraySet<Integer> mForceQueryable; 202 private final WatchedArraySet<String> mProtectedBroadcasts; 203 ParallelComputeComponentVisibility( @onNull ArrayMap<String, ? extends PackageStateInternal> existingSettings, @NonNull WatchedArraySet<Integer> forceQueryable, @NonNull WatchedArraySet<String> protectedBroadcasts)204 ParallelComputeComponentVisibility( 205 @NonNull ArrayMap<String, ? extends PackageStateInternal> existingSettings, 206 @NonNull WatchedArraySet<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 */ execute(@onNull WatchedSparseSetArray<Integer> outQueriesViaComponent)216 void execute(@NonNull WatchedSparseSetArray<Integer> outQueriesViaComponent) { 217 final ExecutorService pool = ConcurrentUtils.newFixedThreadPool( 218 MAX_THREADS, ParallelComputeComponentVisibility.class.getSimpleName(), 219 THREAD_PRIORITY_DEFAULT); 220 try { 221 final List<Pair<PackageState, Future<ArraySet<Integer>>>> futures = 222 new ArrayList<>(); 223 for (int i = mExistingSettings.size() - 1; i >= 0; i--) { 224 final PackageStateInternal setting = mExistingSettings.valueAt(i); 225 final AndroidPackage pkg = setting.getPkg(); 226 if (pkg == null || requestsQueryAllPackages(pkg)) { 227 continue; 228 } 229 if (pkg.getQueriesIntents().isEmpty() 230 && pkg.getQueriesProviders().isEmpty()) { 231 continue; 232 } 233 futures.add(new Pair(setting, 234 pool.submit(() -> getVisibleListOfQueryViaComponents(setting)))); 235 } 236 for (int i = 0; i < futures.size(); i++) { 237 final int appId = futures.get(i).first.getAppId(); 238 final Future<ArraySet<Integer>> future = futures.get(i).second; 239 try { 240 final ArraySet<Integer> visibleList = future.get(); 241 if (visibleList.size() != 0) { 242 outQueriesViaComponent.addAll(appId, visibleList); 243 } 244 } catch (InterruptedException | ExecutionException e) { 245 throw new IllegalStateException(e); 246 } 247 } 248 } finally { 249 pool.shutdownNow(); 250 } 251 } 252 253 /** 254 * Returns a set of app IDs that contains components resolved by the queries intent 255 * or provider that declared in the manifest of the querying package. 256 * 257 * @param setting The package to query. 258 * @return A set of app IDs. 259 */ 260 @NonNull getVisibleListOfQueryViaComponents( @onNull PackageStateInternal setting)261 private ArraySet<Integer> getVisibleListOfQueryViaComponents( 262 @NonNull PackageStateInternal setting) { 263 final ArraySet<Integer> result = new ArraySet(); 264 for (int i = mExistingSettings.size() - 1; i >= 0; i--) { 265 final PackageStateInternal otherSetting = mExistingSettings.valueAt(i); 266 if (setting.getAppId() == otherSetting.getAppId()) { 267 continue; 268 } 269 if (otherSetting.getPkg() == null || mForceQueryable.contains( 270 otherSetting.getAppId())) { 271 continue; 272 } 273 final boolean canQuery = canQueryViaComponents( 274 setting.getPkg(), otherSetting.getPkg(), mProtectedBroadcasts); 275 if (canQuery) { 276 result.add(otherSetting.getAppId()); 277 } 278 } 279 return result; 280 } 281 } 282 } 283