1 /* 2 * Copyright (C) 2021 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 17 package com.android.server.policy; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.app.AppOpsManager; 22 import android.app.AppOpsManager.AttributionFlags; 23 import android.app.AppOpsManagerInternal; 24 import android.app.SyncNotedAppOp; 25 import android.app.role.RoleManager; 26 import android.content.AttributionSource; 27 import android.content.BroadcastReceiver; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.content.pm.PackageManager; 32 import android.content.pm.ResolveInfo; 33 import android.location.LocationManagerInternal; 34 import android.net.Uri; 35 import android.os.Binder; 36 import android.os.IBinder; 37 import android.os.PackageTagsList; 38 import android.os.Process; 39 import android.os.UserHandle; 40 import android.service.voice.VoiceInteractionManagerInternal; 41 import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity; 42 import android.text.TextUtils; 43 import android.util.Log; 44 import android.util.SparseArray; 45 46 import com.android.internal.annotations.GuardedBy; 47 import com.android.internal.util.function.DecFunction; 48 import com.android.internal.util.function.HeptFunction; 49 import com.android.internal.util.function.HexFunction; 50 import com.android.internal.util.function.QuadFunction; 51 import com.android.internal.util.function.QuintConsumer; 52 import com.android.internal.util.function.QuintFunction; 53 import com.android.internal.util.function.TriFunction; 54 import com.android.internal.util.function.UndecFunction; 55 import com.android.server.LocalServices; 56 57 import java.util.Arrays; 58 import java.util.List; 59 import java.util.Map; 60 import java.util.concurrent.ConcurrentHashMap; 61 62 /** 63 * This class defines policy for special behaviors around app ops. 64 */ 65 public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegate { 66 private static final String LOG_TAG = AppOpsPolicy.class.getName(); 67 68 private static final String ACTIVITY_RECOGNITION_TAGS = 69 "android:activity_recognition_allow_listed_tags"; 70 private static final String ACTIVITY_RECOGNITION_TAGS_SEPARATOR = ";"; 71 72 @NonNull 73 private final Object mLock = new Object(); 74 75 @NonNull 76 private final IBinder mToken = new Binder(); 77 78 @NonNull 79 private final Context mContext; 80 81 @NonNull 82 private final RoleManager mRoleManager; 83 84 @NonNull 85 private final VoiceInteractionManagerInternal mVoiceInteractionManagerInternal; 86 87 /** 88 * Whether this device allows only the HotwordDetectionService to use OP_RECORD_AUDIO_HOTWORD 89 * which doesn't incur the privacy indicator. 90 */ 91 private final boolean mIsHotwordDetectionServiceRequired; 92 93 /** 94 * The locking policy around the location tags is a bit special. Since we want to 95 * avoid grabbing the lock on every op note we are taking the approach where the 96 * read and write are being done via a thread-safe data structure such that the 97 * lookup/insert are single thread-safe calls. When we update the cached state we 98 * use a lock to ensure the update's lookup and store calls are done atomically, 99 * so multiple writers would not interleave. The tradeoff is we make is that the 100 * concurrent data structure would use boxing/unboxing of integers but this is 101 * preferred to locking. 102 */ 103 @GuardedBy("mLock - writes only - see above") 104 @NonNull 105 private final ConcurrentHashMap<Integer, PackageTagsList> mLocationTags = 106 new ConcurrentHashMap<>(); 107 108 // location tags can vary per uid - but we merge all tags under an app id into the final data 109 // structure above 110 @GuardedBy("mLock") 111 private final SparseArray<PackageTagsList> mPerUidLocationTags = new SparseArray<>(); 112 113 // activity recognition currently only grabs tags from the APK manifest. we know that the 114 // manifest is the same for all users, so there's no need to track variations in tags across 115 // different users. if that logic ever changes, this might need to behave more like location 116 // tags above. 117 @GuardedBy("mLock - writes only - see above") 118 @NonNull 119 private final ConcurrentHashMap<Integer, PackageTagsList> mActivityRecognitionTags = 120 new ConcurrentHashMap<>(); 121 AppOpsPolicy(@onNull Context context)122 public AppOpsPolicy(@NonNull Context context) { 123 mContext = context; 124 mRoleManager = mContext.getSystemService(RoleManager.class); 125 mVoiceInteractionManagerInternal = LocalServices.getService( 126 VoiceInteractionManagerInternal.class); 127 mIsHotwordDetectionServiceRequired = isHotwordDetectionServiceRequired( 128 mContext.getPackageManager()); 129 130 final LocationManagerInternal locationManagerInternal = LocalServices.getService( 131 LocationManagerInternal.class); 132 locationManagerInternal.setLocationPackageTagsListener( 133 (uid, packageTagsList) -> { 134 synchronized (mLock) { 135 if (packageTagsList.isEmpty()) { 136 mPerUidLocationTags.remove(uid); 137 } else { 138 mPerUidLocationTags.set(uid, packageTagsList); 139 } 140 141 int appId = UserHandle.getAppId(uid); 142 PackageTagsList.Builder appIdTags = new PackageTagsList.Builder(1); 143 int size = mPerUidLocationTags.size(); 144 for (int i = 0; i < size; i++) { 145 if (UserHandle.getAppId(mPerUidLocationTags.keyAt(i)) == appId) { 146 appIdTags.add(mPerUidLocationTags.valueAt(i)); 147 } 148 } 149 150 updateAllowListedTagsForPackageLocked(appId, appIdTags.build(), 151 mLocationTags); 152 } 153 }); 154 155 final IntentFilter intentFilter = new IntentFilter(); 156 intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); 157 intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); 158 intentFilter.addDataScheme("package"); 159 160 context.registerReceiverAsUser(new BroadcastReceiver() { 161 @Override 162 public void onReceive(Context context, Intent intent) { 163 final Uri uri = intent.getData(); 164 if (uri == null) { 165 return; 166 } 167 final String packageName = uri.getSchemeSpecificPart(); 168 if (TextUtils.isEmpty(packageName)) { 169 return; 170 } 171 final List<String> activityRecognizers = mRoleManager.getRoleHolders( 172 RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER); 173 if (activityRecognizers.contains(packageName)) { 174 updateActivityRecognizerTags(packageName); 175 } 176 } 177 }, UserHandle.SYSTEM, intentFilter, null, null); 178 179 mRoleManager.addOnRoleHoldersChangedListenerAsUser(context.getMainExecutor(), 180 (String roleName, UserHandle user) -> { 181 if (RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER.equals(roleName)) { 182 initializeActivityRecognizersTags(); 183 } 184 }, UserHandle.SYSTEM); 185 186 initializeActivityRecognizersTags(); 187 188 // If this device does not have telephony, restrict the phone call ops 189 if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) { 190 AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class); 191 appOps.setUserRestrictionForUser(AppOpsManager.OP_PHONE_CALL_MICROPHONE, true, mToken, 192 null, UserHandle.USER_ALL); 193 appOps.setUserRestrictionForUser(AppOpsManager.OP_PHONE_CALL_CAMERA, true, mToken, 194 null, UserHandle.USER_ALL); 195 } 196 } 197 isHotwordDetectionServiceRequired(PackageManager pm)198 private static boolean isHotwordDetectionServiceRequired(PackageManager pm) { 199 // Usage of the HotwordDetectionService won't be enforced until a later release. 200 return false; 201 } 202 203 @Override checkOperation(int code, int uid, String packageName, @Nullable String attributionTag, boolean raw, QuintFunction<Integer, Integer, String, String, Boolean, Integer> superImpl)204 public int checkOperation(int code, int uid, String packageName, 205 @Nullable String attributionTag, boolean raw, 206 QuintFunction<Integer, Integer, String, String, Boolean, Integer> superImpl) { 207 return superImpl.apply(code, resolveUid(code, uid), packageName, attributionTag, raw); 208 } 209 210 @Override checkAudioOperation(int code, int usage, int uid, String packageName, QuadFunction<Integer, Integer, Integer, String, Integer> superImpl)211 public int checkAudioOperation(int code, int usage, int uid, String packageName, 212 QuadFunction<Integer, Integer, Integer, String, Integer> superImpl) { 213 return superImpl.apply(code, usage, uid, packageName); 214 } 215 216 @Override noteOperation(int code, int uid, @Nullable String packageName, @Nullable String attributionTag, boolean shouldCollectAsyncNotedOp, @Nullable String message, boolean shouldCollectMessage, @NonNull HeptFunction<Integer, Integer, String, String, Boolean, String, Boolean, SyncNotedAppOp> superImpl)217 public SyncNotedAppOp noteOperation(int code, int uid, @Nullable String packageName, 218 @Nullable String attributionTag, boolean shouldCollectAsyncNotedOp, @Nullable 219 String message, boolean shouldCollectMessage, @NonNull HeptFunction<Integer, Integer, 220 String, String, Boolean, String, Boolean, SyncNotedAppOp> superImpl) { 221 return superImpl.apply(resolveDatasourceOp(code, uid, packageName, attributionTag), 222 resolveUid(code, uid), packageName, attributionTag, shouldCollectAsyncNotedOp, 223 message, shouldCollectMessage); 224 } 225 226 @Override noteProxyOperation(int code, @NonNull AttributionSource attributionSource, boolean shouldCollectAsyncNotedOp, @Nullable String message, boolean shouldCollectMessage, boolean skipProxyOperation, @NonNull HexFunction<Integer, AttributionSource, Boolean, String, Boolean, Boolean, SyncNotedAppOp> superImpl)227 public SyncNotedAppOp noteProxyOperation(int code, @NonNull AttributionSource attributionSource, 228 boolean shouldCollectAsyncNotedOp, @Nullable String message, 229 boolean shouldCollectMessage, boolean skipProxyOperation, @NonNull HexFunction<Integer, 230 AttributionSource, Boolean, String, Boolean, Boolean, 231 SyncNotedAppOp> superImpl) { 232 return superImpl.apply(resolveDatasourceOp(code, attributionSource.getUid(), 233 attributionSource.getPackageName(), attributionSource.getAttributionTag()), 234 attributionSource, shouldCollectAsyncNotedOp, message, shouldCollectMessage, 235 skipProxyOperation); 236 } 237 238 @Override startOperation(IBinder token, int code, int uid, @Nullable String packageName, @Nullable String attributionTag, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, @AttributionFlags int attributionFlags, int attributionChainId, @NonNull UndecFunction<IBinder, Integer, Integer, String, String, Boolean, Boolean, String, Boolean, Integer, Integer, SyncNotedAppOp> superImpl)239 public SyncNotedAppOp startOperation(IBinder token, int code, int uid, 240 @Nullable String packageName, @Nullable String attributionTag, 241 boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message, 242 boolean shouldCollectMessage, @AttributionFlags int attributionFlags, 243 int attributionChainId, @NonNull UndecFunction<IBinder, Integer, Integer, String, 244 String, Boolean, Boolean, String, Boolean, Integer, Integer, 245 SyncNotedAppOp> superImpl) { 246 return superImpl.apply(token, resolveDatasourceOp(code, uid, packageName, attributionTag), 247 resolveUid(code, uid), packageName, attributionTag, startIfModeDefault, 248 shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags, 249 attributionChainId); 250 } 251 252 @Override startProxyOperation(int code, @NonNull AttributionSource attributionSource, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags, int attributionChainId, @NonNull DecFunction<Integer, AttributionSource, Boolean, Boolean, String, Boolean, Boolean, Integer, Integer, Integer, SyncNotedAppOp> superImpl)253 public SyncNotedAppOp startProxyOperation(int code, 254 @NonNull AttributionSource attributionSource, boolean startIfModeDefault, 255 boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, 256 boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags, 257 @AttributionFlags int proxiedAttributionFlags, int attributionChainId, 258 @NonNull DecFunction<Integer, AttributionSource, Boolean, Boolean, String, Boolean, 259 Boolean, Integer, Integer, Integer, SyncNotedAppOp> superImpl) { 260 return superImpl.apply(resolveDatasourceOp(code, attributionSource.getUid(), 261 attributionSource.getPackageName(), attributionSource.getAttributionTag()), 262 attributionSource, startIfModeDefault, shouldCollectAsyncNotedOp, message, 263 shouldCollectMessage, skipProxyOperation, proxyAttributionFlags, 264 proxiedAttributionFlags, attributionChainId); 265 } 266 267 @Override finishOperation(IBinder clientId, int code, int uid, String packageName, String attributionTag, @NonNull QuintConsumer<IBinder, Integer, Integer, String, String> superImpl)268 public void finishOperation(IBinder clientId, int code, int uid, String packageName, 269 String attributionTag, 270 @NonNull QuintConsumer<IBinder, Integer, Integer, String, String> superImpl) { 271 superImpl.accept(clientId, resolveDatasourceOp(code, uid, packageName, attributionTag), 272 resolveUid(code, uid), packageName, attributionTag); 273 } 274 275 @Override finishProxyOperation(int code, @NonNull AttributionSource attributionSource, boolean skipProxyOperation, @NonNull TriFunction<Integer, AttributionSource, Boolean, Void> superImpl)276 public void finishProxyOperation(int code, @NonNull AttributionSource attributionSource, 277 boolean skipProxyOperation, @NonNull TriFunction<Integer, AttributionSource, 278 Boolean, Void> superImpl) { 279 superImpl.apply(resolveDatasourceOp(code, attributionSource.getUid(), 280 attributionSource.getPackageName(), attributionSource.getAttributionTag()), 281 attributionSource, skipProxyOperation); 282 } 283 resolveDatasourceOp(int code, int uid, @NonNull String packageName, @Nullable String attributionTag)284 private int resolveDatasourceOp(int code, int uid, @NonNull String packageName, 285 @Nullable String attributionTag) { 286 code = resolveRecordAudioOp(code, uid); 287 if (attributionTag == null) { 288 return code; 289 } 290 int resolvedCode = resolveLocationOp(code); 291 if (resolvedCode != code) { 292 if (isDatasourceAttributionTag(uid, packageName, attributionTag, 293 mLocationTags)) { 294 return resolvedCode; 295 } 296 } else { 297 resolvedCode = resolveArOp(code); 298 if (resolvedCode != code) { 299 if (isDatasourceAttributionTag(uid, packageName, attributionTag, 300 mActivityRecognitionTags)) { 301 return resolvedCode; 302 } 303 } 304 } 305 return code; 306 } 307 initializeActivityRecognizersTags()308 private void initializeActivityRecognizersTags() { 309 final List<String> activityRecognizers = mRoleManager.getRoleHolders( 310 RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER); 311 final int recognizerCount = activityRecognizers.size(); 312 if (recognizerCount > 0) { 313 for (int i = 0; i < recognizerCount; i++) { 314 final String activityRecognizer = activityRecognizers.get(i); 315 updateActivityRecognizerTags(activityRecognizer); 316 } 317 } else { 318 clearActivityRecognitionTags(); 319 } 320 } 321 clearActivityRecognitionTags()322 private void clearActivityRecognitionTags() { 323 synchronized (mLock) { 324 mActivityRecognitionTags.clear(); 325 } 326 } 327 updateActivityRecognizerTags(@onNull String activityRecognizer)328 private void updateActivityRecognizerTags(@NonNull String activityRecognizer) { 329 final int flags = PackageManager.GET_SERVICES 330 | PackageManager.GET_META_DATA 331 | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS 332 | PackageManager.MATCH_DIRECT_BOOT_AWARE 333 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; 334 335 final Intent intent = new Intent(Intent.ACTION_ACTIVITY_RECOGNIZER); 336 intent.setPackage(activityRecognizer); 337 final ResolveInfo resolvedService = mContext.getPackageManager() 338 .resolveServiceAsUser(intent, flags, UserHandle.USER_SYSTEM); 339 if (resolvedService == null || resolvedService.serviceInfo == null) { 340 Log.w(LOG_TAG, "Service recognizer doesn't handle " 341 + Intent.ACTION_ACTIVITY_RECOGNIZER + ", ignoring!"); 342 return; 343 } 344 final String tagsList = resolvedService.serviceInfo.metaData.getString( 345 ACTIVITY_RECOGNITION_TAGS); 346 if (!TextUtils.isEmpty(tagsList)) { 347 PackageTagsList packageTagsList = new PackageTagsList.Builder(1).add( 348 resolvedService.serviceInfo.packageName, 349 Arrays.asList(tagsList.split(ACTIVITY_RECOGNITION_TAGS_SEPARATOR))).build(); 350 synchronized (mLock) { 351 updateAllowListedTagsForPackageLocked( 352 UserHandle.getAppId(resolvedService.serviceInfo.applicationInfo.uid), 353 packageTagsList, 354 mActivityRecognitionTags); 355 } 356 } 357 } 358 updateAllowListedTagsForPackageLocked(int appId, PackageTagsList packageTagsList, ConcurrentHashMap<Integer, PackageTagsList> datastore)359 private static void updateAllowListedTagsForPackageLocked(int appId, 360 PackageTagsList packageTagsList, 361 ConcurrentHashMap<Integer, PackageTagsList> datastore) { 362 datastore.put(appId, packageTagsList); 363 } 364 isDatasourceAttributionTag(int uid, @NonNull String packageName, @NonNull String attributionTag, @NonNull Map<Integer, PackageTagsList> mappedOps)365 private static boolean isDatasourceAttributionTag(int uid, @NonNull String packageName, 366 @NonNull String attributionTag, @NonNull Map<Integer, PackageTagsList> mappedOps) { 367 // Only a single lookup from the underlying concurrent data structure 368 final PackageTagsList appIdTags = mappedOps.get(UserHandle.getAppId(uid)); 369 return appIdTags != null && appIdTags.contains(packageName, attributionTag); 370 } 371 resolveLocationOp(int code)372 private static int resolveLocationOp(int code) { 373 switch (code) { 374 case AppOpsManager.OP_FINE_LOCATION: 375 return AppOpsManager.OP_FINE_LOCATION_SOURCE; 376 case AppOpsManager.OP_COARSE_LOCATION: 377 return AppOpsManager.OP_COARSE_LOCATION_SOURCE; 378 } 379 return code; 380 } 381 resolveArOp(int code)382 private static int resolveArOp(int code) { 383 if (code == AppOpsManager.OP_ACTIVITY_RECOGNITION) { 384 return AppOpsManager.OP_ACTIVITY_RECOGNITION_SOURCE; 385 } 386 return code; 387 } 388 resolveRecordAudioOp(int code, int uid)389 private int resolveRecordAudioOp(int code, int uid) { 390 if (code == AppOpsManager.OP_RECORD_AUDIO_HOTWORD) { 391 if (!mIsHotwordDetectionServiceRequired) { 392 return code; 393 } 394 // Only the HotwordDetectionService can use the HOTWORD op which doesn't incur the 395 // privacy indicator. Downgrade to standard RECORD_AUDIO for other processes. 396 final HotwordDetectionServiceIdentity hotwordDetectionServiceIdentity = 397 mVoiceInteractionManagerInternal.getHotwordDetectionServiceIdentity(); 398 if (hotwordDetectionServiceIdentity != null 399 && uid == hotwordDetectionServiceIdentity.getIsolatedUid()) { 400 return code; 401 } 402 return AppOpsManager.OP_RECORD_AUDIO; 403 } 404 return code; 405 } 406 resolveUid(int code, int uid)407 private int resolveUid(int code, int uid) { 408 // The HotwordDetectionService is an isolated service, which ordinarily cannot hold 409 // permissions. So we allow it to assume the owning package identity for certain 410 // operations. 411 // Note: The package name coming from the audio server is already the one for the owning 412 // package, so we don't need to modify it. 413 if (Process.isIsolated(uid) // simple check which fails-fast for the common case 414 && (code == AppOpsManager.OP_RECORD_AUDIO 415 || code == AppOpsManager.OP_RECORD_AUDIO_HOTWORD)) { 416 final HotwordDetectionServiceIdentity hotwordDetectionServiceIdentity = 417 mVoiceInteractionManagerInternal.getHotwordDetectionServiceIdentity(); 418 if (hotwordDetectionServiceIdentity != null 419 && uid == hotwordDetectionServiceIdentity.getIsolatedUid()) { 420 uid = hotwordDetectionServiceIdentity.getOwnerUid(); 421 } 422 } 423 return uid; 424 } 425 } 426