• 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.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