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