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