• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 package com.android.server.appsearch.util;
17 
18 import static android.app.appsearch.AppSearchResult.RESULT_RATE_LIMITED;
19 import static android.app.appsearch.AppSearchResult.throwableToFailedResult;
20 
21 import android.Manifest;
22 import android.annotation.BinderThread;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.app.appsearch.AppSearchBatchResult;
26 import android.app.appsearch.AppSearchResult;
27 import android.app.appsearch.aidl.AppSearchBatchResultParcel;
28 import android.app.appsearch.aidl.AppSearchResultParcel;
29 import android.app.appsearch.aidl.IAppSearchBatchResultCallback;
30 import android.app.appsearch.aidl.IAppSearchResultCallback;
31 import android.content.AttributionSource;
32 import android.content.Context;
33 import android.content.pm.PackageManager;
34 import android.os.Binder;
35 import android.os.RemoteException;
36 import android.os.UserHandle;
37 import android.os.UserManager;
38 import android.util.ArraySet;
39 import android.util.Log;
40 
41 import com.android.internal.annotations.GuardedBy;
42 import com.android.server.appsearch.AppSearchEnvironmentFactory;
43 import com.android.server.appsearch.external.localstorage.stats.CallStats;
44 
45 import java.util.Objects;
46 import java.util.Set;
47 import java.util.concurrent.Executor;
48 
49 /**
50  * Utilities to help with implementing AppSearch's services.
51  * @hide
52  */
53 public class ServiceImplHelper {
54     private static final String TAG = "AppSearchServiceUtil";
55 
56     private final Context mContext;
57     private final UserManager mUserManager;
58     private final ExecutorManager mExecutorManager;
59 
60     // Cache of unlocked users so we don't have to query UserManager service each time. The "locked"
61     // suffix refers to the fact that access to the field should be locked; unrelated to the
62     // unlocked status of users.
63     @GuardedBy("mUnlockedUsersLocked")
64     private final Set<UserHandle> mUnlockedUsersLocked = new ArraySet<>();
65 
ServiceImplHelper(@onNull Context context, @NonNull ExecutorManager executorManager)66     public ServiceImplHelper(@NonNull Context context, @NonNull ExecutorManager executorManager) {
67         mContext = Objects.requireNonNull(context);
68         mUserManager = context.getSystemService(UserManager.class);
69         mExecutorManager = Objects.requireNonNull(executorManager);
70     }
71 
setUserIsLocked(@onNull UserHandle userHandle, boolean isLocked)72     public void setUserIsLocked(@NonNull UserHandle userHandle, boolean isLocked) {
73         synchronized (mUnlockedUsersLocked) {
74             if (isLocked) {
75                 mUnlockedUsersLocked.remove(userHandle);
76             } else {
77                 mUnlockedUsersLocked.add(userHandle);
78             }
79         }
80     }
81 
isUserLocked(@onNull UserHandle callingUser)82     public boolean isUserLocked(@NonNull UserHandle callingUser) {
83         synchronized (mUnlockedUsersLocked) {
84             // First, check the local copy.
85             if (mUnlockedUsersLocked.contains(callingUser)) {
86                 return false;
87             }
88             // If the local copy says the user is locked, check with UM for the actual state,
89             // since the user might just have been unlocked.
90             return !mUserManager.isUserUnlockingOrUnlocked(callingUser);
91         }
92     }
93 
verifyUserUnlocked(@onNull UserHandle callingUser)94     public void verifyUserUnlocked(@NonNull UserHandle callingUser) {
95         if (isUserLocked(callingUser)) {
96             throw new IllegalStateException(callingUser + " is locked or not running.");
97         }
98     }
99 
100     /**
101      * Verifies that the information about the caller matches Binder's settings, determines a final
102      * user that the call is allowed to run as, and checks that the user is unlocked.
103      *
104      * <p>If these checks fail, returns {@code null} and sends the error to the given callback.
105      *
106      * <p>This method must be called on the binder thread.
107      *
108      * @return The result containing the final verified user that the call should run as, if all
109      * checks pass. Otherwise return null.
110      */
111     @BinderThread
112     @Nullable
verifyIncomingCallWithCallback( @onNull AttributionSource callerAttributionSource, @NonNull UserHandle userHandle, @NonNull IAppSearchResultCallback errorCallback)113     public UserHandle verifyIncomingCallWithCallback(
114             @NonNull AttributionSource callerAttributionSource,
115             @NonNull UserHandle userHandle,
116             @NonNull IAppSearchResultCallback errorCallback) {
117         try {
118             return verifyIncomingCall(callerAttributionSource, userHandle);
119         } catch (Throwable t) {
120             invokeCallbackOnResult(errorCallback, throwableToFailedResult(t));
121             return null;
122         }
123     }
124 
125     /**
126      * Verifies that the information about the caller matches Binder's settings, determines a final
127      * user that the call is allowed to run as, and checks that the user is unlocked.
128      *
129      * <p>If these checks fail, returns {@code null} and sends the error to the given callback.
130      *
131      * <p>This method must be called on the binder thread.
132      *
133      * @return The result containing the final verified user that the call should run as, if all
134      * checks pass. Otherwise return null.
135      */
136     @BinderThread
137     @Nullable
verifyIncomingCallWithCallback( @onNull AttributionSource callerAttributionSource, @NonNull UserHandle userHandle, @NonNull IAppSearchBatchResultCallback errorCallback)138     public UserHandle verifyIncomingCallWithCallback(
139             @NonNull AttributionSource callerAttributionSource,
140             @NonNull UserHandle userHandle,
141             @NonNull IAppSearchBatchResultCallback errorCallback) {
142         try {
143             return verifyIncomingCall(callerAttributionSource, userHandle);
144         } catch (Throwable t) {
145             invokeCallbackOnError(errorCallback, t);
146             return null;
147         }
148     }
149 
150     /**
151      * Verifies that the information about the caller matches Binder's settings, determines a final
152      * user that the call is allowed to run as, and checks that the user is unlocked.
153      *
154      * <p>This method must be called on the binder thread.
155      *
156      * @return The final verified user that the caller should act as
157      * @throws RuntimeException if validation fails
158      */
159     @BinderThread
160     @NonNull
verifyIncomingCall( @onNull AttributionSource callerAttributionSource, @NonNull UserHandle userHandle)161     public UserHandle verifyIncomingCall(
162             @NonNull AttributionSource callerAttributionSource, @NonNull UserHandle userHandle) {
163         Objects.requireNonNull(callerAttributionSource);
164         Objects.requireNonNull(userHandle);
165 
166         int callingPid = Binder.getCallingPid();
167         int callingUid = Binder.getCallingUid();
168         long callingIdentity = Binder.clearCallingIdentity();
169         try {
170             verifyCaller(callingUid, callerAttributionSource);
171             String callingPackageName =
172                 Objects.requireNonNull(callerAttributionSource.getPackageName());
173             UserHandle targetUser =
174                 handleIncomingUser(callingPackageName, userHandle, callingPid, callingUid);
175             verifyUserUnlocked(targetUser);
176             return targetUser;
177         } finally {
178             Binder.restoreCallingIdentity(callingIdentity);
179         }
180     }
181 
182     /**
183      * Verify various aspects of the calling user.
184      * @param callingUid Uid of the caller, usually retrieved from Binder for authenticity.
185      * @param callerAttributionSource The permission identity of the caller
186      */
verifyCaller(int callingUid, @NonNull AttributionSource callerAttributionSource)187     private void verifyCaller(int callingUid, @NonNull AttributionSource callerAttributionSource) {
188         // Check does the attribution source is one for the calling app.
189         callerAttributionSource.enforceCallingUid();
190         // Obtain the user where the client is running in. Note that this could be different from
191         // the userHandle where the client wants to run the AppSearch operation in.
192         UserHandle callingUserHandle = UserHandle.getUserHandleForUid(callingUid);
193         Context callingUserContext = AppSearchEnvironmentFactory
194             .getEnvironmentInstance()
195             .createContextAsUser(mContext, callingUserHandle);
196         String callingPackageName =
197             Objects.requireNonNull(callerAttributionSource.getPackageName());
198         verifyCallingPackage(callingUserContext, callingUid, callingPackageName);
199         verifyNotInstantApp(callingUserContext, callingPackageName);
200     }
201 
202     /**
203      * Check that the caller's supposed package name matches the uid making the call.
204      *
205      * @throws SecurityException if the package name and uid don't match.
206      */
verifyCallingPackage( @onNull Context actualCallingUserContext, int actualCallingUid, @NonNull String claimedCallingPackage)207     private void verifyCallingPackage(
208             @NonNull Context actualCallingUserContext,
209             int actualCallingUid,
210             @NonNull String claimedCallingPackage) {
211         int claimedCallingUid = PackageUtil.getPackageUid(
212                 actualCallingUserContext, claimedCallingPackage);
213         if (claimedCallingUid != actualCallingUid) {
214             throw new SecurityException(
215                     "Specified calling package ["
216                             + claimedCallingPackage
217                             + "] does not match the calling uid "
218                             + actualCallingUid);
219         }
220     }
221 
222     /**
223      * Ensure instant apps can't make calls to AppSearch.
224      *
225      * @throws SecurityException if the caller is an instant app.
226      */
verifyNotInstantApp(@onNull Context userContext, @NonNull String packageName)227     private void verifyNotInstantApp(@NonNull Context userContext, @NonNull String packageName) {
228         PackageManager callingPackageManager = userContext.getPackageManager();
229         if (callingPackageManager.isInstantApp(packageName)) {
230             throw new SecurityException("Caller not allowed to create AppSearch session"
231                     + "; userHandle=" + userContext.getUser() + ", callingPackage=" + packageName);
232         }
233     }
234 
235     /**
236      * Helper for dealing with incoming user arguments to system service calls.
237      *
238      * <p>Takes care of checking permissions and if the target is special user, this method will
239      * simply throw.
240      *
241      * @param callingPackageName The package name of the caller.
242      * @param targetUserHandle The user which the caller is requesting to execute as.
243      * @param callingPid The actual pid of the caller as determined by Binder.
244      * @param callingUid The actual uid of the caller as determined by Binder.
245      *
246      * @return the user handle that the call should run as. Will always be a concrete user.
247      *
248      * @throws IllegalArgumentException if the target user is a special user.
249      * @throws SecurityException if caller trying to interact across user without
250      * {@link Manifest.permission#INTERACT_ACROSS_USERS_FULL}
251      */
252     @NonNull
handleIncomingUser(@onNull String callingPackageName, @NonNull UserHandle targetUserHandle, int callingPid, int callingUid)253     private UserHandle handleIncomingUser(@NonNull String callingPackageName,
254             @NonNull UserHandle targetUserHandle, int callingPid, int callingUid) {
255         UserHandle callingUserHandle = UserHandle.getUserHandleForUid(callingUid);
256         if (callingUserHandle.equals(targetUserHandle)) {
257             return targetUserHandle;
258         }
259 
260         // Duplicates UserController#ensureNotSpecialUser
261         if (targetUserHandle.getIdentifier() < 0) {
262             throw new IllegalArgumentException(
263                     "Call does not support special user " + targetUserHandle);
264         }
265 
266         if (mContext.checkPermission(
267                 Manifest.permission.INTERACT_ACROSS_USERS_FULL,
268                 callingPid,
269                 callingUid) == PackageManager.PERMISSION_GRANTED) {try {
270             // Normally if the calling package doesn't exist in the target user, user cannot
271             // call AppSearch. But since the SDK side cannot be trusted, we still need to verify
272             // the calling package exists in the target user.
273             // We need to create the package context for the targetUser, and this call will fail
274             // if the calling package doesn't exist in the target user.
275             mContext.createPackageContextAsUser(callingPackageName, /*flags=*/0,
276                     targetUserHandle);
277         } catch (PackageManager.NameNotFoundException e) {
278             throw new SecurityException(
279                     "Package: " + callingPackageName + " haven't installed for user "
280                             + targetUserHandle.getIdentifier());
281         }
282             return targetUserHandle;
283         }
284         throw new SecurityException(
285                 "Permission denied while calling from uid " + callingUid
286                         + " with " + targetUserHandle + "; Requires permission: "
287                         + Manifest.permission.INTERACT_ACROSS_USERS_FULL);
288     }
289 
290     /**
291      * Helper to execute the implementation of some AppSearch functionality on the executor for that
292      * user.
293      *
294      * <p>You should first make sure the call is allowed to run using {@link #verifyCaller}.
295      *
296      * @param targetUser            The verified user the call should run as, as determined by
297      *                              {@link #verifyCaller}.
298      * @param errorCallback         Callback to complete with an error if starting the lambda fails.
299      *                              Otherwise this callback is not triggered.
300      * @param callingPackageName    Package making this lambda call.
301      * @param apiType               Api type of this lambda call.
302      * @param lambda                The lambda to execute on the user-provided executor.
303      *
304      * @return true if the call is accepted by the executor and false otherwise.
305      */
306     @BinderThread
executeLambdaForUserAsync( @onNull UserHandle targetUser, @NonNull IAppSearchResultCallback errorCallback, @NonNull String callingPackageName, @CallStats.CallType int apiType, @NonNull Runnable lambda)307     public boolean executeLambdaForUserAsync(
308             @NonNull UserHandle targetUser,
309             @NonNull IAppSearchResultCallback errorCallback,
310             @NonNull String callingPackageName,
311             @CallStats.CallType int apiType,
312             @NonNull Runnable lambda) {
313         Objects.requireNonNull(targetUser);
314         Objects.requireNonNull(errorCallback);
315         Objects.requireNonNull(callingPackageName);
316         Objects.requireNonNull(lambda);
317         try {
318             Executor executor = mExecutorManager.getOrCreateUserExecutor(targetUser);
319             if (executor instanceof RateLimitedExecutor) {
320                 boolean callAccepted = ((RateLimitedExecutor) executor).execute(lambda,
321                         callingPackageName, apiType);
322                 if (!callAccepted) {
323                     invokeCallbackOnResult(errorCallback,
324                             AppSearchResult.newFailedResult(RESULT_RATE_LIMITED,
325                                     "AppSearch rate limit reached."));
326                     return false;
327                 }
328             } else {
329                 executor.execute(lambda);
330             }
331         } catch (Throwable t) {
332             invokeCallbackOnResult(errorCallback, throwableToFailedResult(t));
333         }
334         return true;
335     }
336 
337     /**
338      * Helper to execute the implementation of some AppSearch functionality on the executor for that
339      * user.
340      *
341      * <p>You should first make sure the call is allowed to run using {@link #verifyCaller}.
342      *
343      * @param targetUser            The verified user the call should run as, as determined by
344      *                              {@link #verifyCaller}.
345      * @param errorCallback         Callback to complete with an error if starting the lambda fails.
346      *                              Otherwise this callback is not triggered.
347      * @param callingPackageName    Package making this lambda call.
348      * @param apiType               Api type of this lambda call.
349      * @param lambda                The lambda to execute on the user-provided executor.
350      *
351      * @return true if the call is accepted by the executor and false otherwise.
352      */
353     @BinderThread
executeLambdaForUserAsync( @onNull UserHandle targetUser, @NonNull IAppSearchBatchResultCallback errorCallback, @NonNull String callingPackageName, @CallStats.CallType int apiType, @NonNull Runnable lambda)354     public boolean executeLambdaForUserAsync(
355             @NonNull UserHandle targetUser,
356             @NonNull IAppSearchBatchResultCallback errorCallback,
357             @NonNull String callingPackageName,
358             @CallStats.CallType int apiType,
359             @NonNull Runnable lambda) {
360         Objects.requireNonNull(targetUser);
361         Objects.requireNonNull(errorCallback);
362         Objects.requireNonNull(callingPackageName);
363         Objects.requireNonNull(lambda);
364         try {
365             Executor executor = mExecutorManager.getOrCreateUserExecutor(targetUser);
366             if (executor instanceof RateLimitedExecutor) {
367                 boolean callAccepted = ((RateLimitedExecutor) executor).execute(lambda,
368                         callingPackageName, apiType);
369                 if (!callAccepted) {
370                     invokeCallbackOnError(errorCallback,
371                             AppSearchResult.newFailedResult(RESULT_RATE_LIMITED,
372                                     "AppSearch rate limit reached."));
373                     return false;
374                 }
375             } else {
376                 executor.execute(lambda);
377             }
378         } catch (Throwable t) {
379             invokeCallbackOnError(errorCallback, t);
380         }
381         return true;
382     }
383 
384     /**
385      * Helper to execute the implementation of some AppSearch functionality on the executor for that
386      * user, without invoking callback for the user.
387      *
388      * <p>You should first make sure the call is allowed to run using {@link #verifyCaller}.
389      *
390      * @param targetUser         The verified user the call should run as, as determined by
391      *                           {@link #verifyCaller}.
392      * @param callingPackageName Package making this lambda call.
393      * @param apiType            Api type of this lambda call.
394      * @param lambda             The lambda to execute on the user-provided executor.
395      *
396      * @return true if the call is accepted by the executor and false otherwise.
397      */
398     @BinderThread
executeLambdaForUserNoCallbackAsync( @onNull UserHandle targetUser, @NonNull String callingPackageName, @CallStats.CallType int apiType, @NonNull Runnable lambda)399     public boolean executeLambdaForUserNoCallbackAsync(
400             @NonNull UserHandle targetUser,
401             @NonNull String callingPackageName,
402             @CallStats.CallType int apiType,
403             @NonNull Runnable lambda) {
404         Objects.requireNonNull(targetUser);
405         Objects.requireNonNull(callingPackageName);
406         Objects.requireNonNull(lambda);
407         Executor executor = mExecutorManager.getOrCreateUserExecutor(targetUser);
408         if (executor instanceof RateLimitedExecutor) {
409             return ((RateLimitedExecutor) executor).execute(lambda, callingPackageName,
410                     apiType);
411         } else {
412             executor.execute(lambda);
413             return true;
414         }
415     }
416 
417     /** Invokes the {@link IAppSearchResultCallback} with the result. */
invokeCallbackOnResult( IAppSearchResultCallback callback, AppSearchResult<?> result)418     public static void invokeCallbackOnResult(
419             IAppSearchResultCallback callback, AppSearchResult<?> result) {
420         try {
421             callback.onResult(new AppSearchResultParcel<>(result));
422         } catch (RemoteException e) {
423             Log.e(TAG, "Unable to send result to the callback", e);
424         }
425     }
426 
427     /** Invokes the {@link IAppSearchBatchResultCallback} with the result. */
invokeCallbackOnResult( IAppSearchBatchResultCallback callback, AppSearchBatchResult<String, ?> result)428     public static void invokeCallbackOnResult(
429             IAppSearchBatchResultCallback callback, AppSearchBatchResult<String, ?> result) {
430         try {
431             callback.onResult(new AppSearchBatchResultParcel<>(result));
432         } catch (RemoteException e) {
433             Log.e(TAG, "Unable to send result to the callback", e);
434         }
435     }
436 
437     /**
438      * Invokes the {@link IAppSearchBatchResultCallback} with an unexpected internal throwable.
439      *
440      * <p>The throwable is converted to {@link AppSearchResult}.
441      */
invokeCallbackOnError( @onNull IAppSearchBatchResultCallback callback, @NonNull Throwable throwable)442     public static void invokeCallbackOnError(
443             @NonNull IAppSearchBatchResultCallback callback, @NonNull Throwable throwable) {
444         invokeCallbackOnError(callback, throwableToFailedResult(throwable));
445     }
446 
447     /**
448      * Invokes the {@link IAppSearchBatchResultCallback} with the error result.
449      */
invokeCallbackOnError( @onNull IAppSearchBatchResultCallback callback, @NonNull AppSearchResult<?> result)450     public static void invokeCallbackOnError(
451             @NonNull IAppSearchBatchResultCallback callback, @NonNull AppSearchResult<?> result) {
452         try {
453             callback.onSystemError(new AppSearchResultParcel<>(result));
454         } catch (RemoteException e) {
455             Log.e(TAG, "Unable to send error to the callback", e);
456         }
457     }
458 }
459