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