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