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