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.Bundle; 37 import android.os.IBinder; 38 import android.os.PackageTagsList; 39 import android.os.Process; 40 import android.os.UserHandle; 41 import android.service.voice.VoiceInteractionManagerInternal; 42 import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity; 43 import android.text.TextUtils; 44 import android.util.Log; 45 import android.util.SparseArray; 46 47 import com.android.internal.annotations.GuardedBy; 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.UndecFunction; 54 import com.android.server.LocalServices; 55 56 import java.io.PrintWriter; 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 89 * OP_RECORD_AUDIO_HOTWORD 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 // Restrict phone call ops if the TelecomService will not start (conditioned on having 189 // FEATURE_MICROPHONE, FEATURE_TELECOM, or FEATURE_TELEPHONY). 190 PackageManager pm = mContext.getPackageManager(); 191 if (!pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) 192 && !pm.hasSystemFeature(PackageManager.FEATURE_MICROPHONE) 193 && !pm.hasSystemFeature(PackageManager.FEATURE_TELECOM)) { 194 AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class); 195 appOps.setUserRestrictionForUser(AppOpsManager.OP_PHONE_CALL_MICROPHONE, true, mToken, 196 null, UserHandle.USER_ALL); 197 appOps.setUserRestrictionForUser(AppOpsManager.OP_PHONE_CALL_CAMERA, true, mToken, 198 null, UserHandle.USER_ALL); 199 } 200 } 201 isHotwordDetectionServiceRequired(PackageManager pm)202 private static boolean isHotwordDetectionServiceRequired(PackageManager pm) { 203 // Usage of the HotwordDetectionService won't be enforced until a later release. 204 return false; 205 } 206 207 @Override checkOperation(int code, int uid, String packageName, @Nullable String attributionTag, boolean raw, QuintFunction<Integer, Integer, String, String, Boolean, Integer> superImpl)208 public int checkOperation(int code, int uid, String packageName, 209 @Nullable String attributionTag, boolean raw, 210 QuintFunction<Integer, Integer, String, String, Boolean, Integer> superImpl) { 211 return superImpl.apply(code, resolveUid(code, uid), packageName, attributionTag, raw); 212 } 213 214 @Override checkAudioOperation(int code, int usage, int uid, String packageName, QuadFunction<Integer, Integer, Integer, String, Integer> superImpl)215 public int checkAudioOperation(int code, int usage, int uid, String packageName, 216 QuadFunction<Integer, Integer, Integer, String, Integer> superImpl) { 217 return superImpl.apply(code, usage, uid, packageName); 218 } 219 220 @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)221 public SyncNotedAppOp noteOperation(int code, int uid, @Nullable String packageName, 222 @Nullable String attributionTag, boolean shouldCollectAsyncNotedOp, @Nullable 223 String message, boolean shouldCollectMessage, @NonNull HeptFunction<Integer, Integer, 224 String, String, Boolean, String, Boolean, SyncNotedAppOp> superImpl) { 225 return superImpl.apply(resolveDatasourceOp(code, uid, packageName, attributionTag), 226 resolveUid(code, uid), packageName, attributionTag, shouldCollectAsyncNotedOp, 227 message, shouldCollectMessage); 228 } 229 230 @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)231 public SyncNotedAppOp noteProxyOperation(int code, @NonNull AttributionSource attributionSource, 232 boolean shouldCollectAsyncNotedOp, @Nullable String message, 233 boolean shouldCollectMessage, boolean skipProxyOperation, @NonNull HexFunction<Integer, 234 AttributionSource, Boolean, String, Boolean, Boolean, 235 SyncNotedAppOp> superImpl) { 236 return superImpl.apply(resolveDatasourceOp(code, attributionSource.getUid(), 237 attributionSource.getPackageName(), attributionSource.getAttributionTag()), 238 attributionSource, shouldCollectAsyncNotedOp, message, shouldCollectMessage, 239 skipProxyOperation); 240 } 241 242 @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)243 public SyncNotedAppOp startOperation(IBinder token, int code, int uid, 244 @Nullable String packageName, @Nullable String attributionTag, 245 boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message, 246 boolean shouldCollectMessage, @AttributionFlags int attributionFlags, 247 int attributionChainId, @NonNull UndecFunction<IBinder, Integer, Integer, String, 248 String, Boolean, Boolean, String, Boolean, Integer, Integer, 249 SyncNotedAppOp> superImpl) { 250 return superImpl.apply(token, resolveDatasourceOp(code, uid, packageName, attributionTag), 251 resolveUid(code, uid), packageName, attributionTag, startIfModeDefault, 252 shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags, 253 attributionChainId); 254 } 255 256 @Override startProxyOperation(@onNull IBinder clientId, int code, @NonNull AttributionSource attributionSource, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags, int attributionChainId, @NonNull UndecFunction<IBinder, Integer, AttributionSource, Boolean, Boolean, String, Boolean, Boolean, Integer, Integer, Integer, SyncNotedAppOp> superImpl)257 public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code, 258 @NonNull AttributionSource attributionSource, boolean startIfModeDefault, 259 boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, 260 boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags, 261 @AttributionFlags int proxiedAttributionFlags, int attributionChainId, 262 @NonNull UndecFunction<IBinder, Integer, AttributionSource, Boolean, Boolean, String, 263 Boolean, Boolean, Integer, Integer, Integer, SyncNotedAppOp> superImpl) { 264 return superImpl.apply(clientId, resolveDatasourceOp(code, attributionSource.getUid(), 265 attributionSource.getPackageName(), attributionSource.getAttributionTag()), 266 attributionSource, startIfModeDefault, shouldCollectAsyncNotedOp, message, 267 shouldCollectMessage, skipProxyOperation, proxyAttributionFlags, 268 proxiedAttributionFlags, attributionChainId); 269 } 270 271 @Override finishOperation(IBinder clientId, int code, int uid, String packageName, String attributionTag, @NonNull QuintConsumer<IBinder, Integer, Integer, String, String> superImpl)272 public void finishOperation(IBinder clientId, int code, int uid, String packageName, 273 String attributionTag, 274 @NonNull QuintConsumer<IBinder, Integer, Integer, String, String> superImpl) { 275 superImpl.accept(clientId, resolveDatasourceOp(code, uid, packageName, attributionTag), 276 resolveUid(code, uid), packageName, attributionTag); 277 } 278 279 @Override finishProxyOperation(@onNull IBinder clientId, int code, @NonNull AttributionSource attributionSource, boolean skipProxyOperation, @NonNull QuadFunction<IBinder, Integer, AttributionSource, Boolean, Void> superImpl)280 public void finishProxyOperation(@NonNull IBinder clientId, int code, 281 @NonNull AttributionSource attributionSource, boolean skipProxyOperation, 282 @NonNull QuadFunction<IBinder, Integer, AttributionSource, Boolean, Void> superImpl) { 283 superImpl.apply(clientId, resolveDatasourceOp(code, attributionSource.getUid(), 284 attributionSource.getPackageName(), attributionSource.getAttributionTag()), 285 attributionSource, skipProxyOperation); 286 } 287 288 /** 289 * Write location and activity recognition tags to console. 290 * See also {@code adb shell dumpsys appops}. 291 */ dumpTags(PrintWriter writer)292 public void dumpTags(PrintWriter writer) { 293 if (!mLocationTags.isEmpty()) { 294 writer.println(" AppOps policy location tags:"); 295 writeTags(mLocationTags, writer); 296 writer.println(); 297 } 298 if (!mActivityRecognitionTags.isEmpty()) { 299 writer.println(" AppOps policy activity recognition tags:"); 300 writeTags(mActivityRecognitionTags, writer); 301 writer.println(); 302 } 303 } 304 writeTags(Map<Integer, PackageTagsList> tags, PrintWriter writer)305 private void writeTags(Map<Integer, PackageTagsList> tags, PrintWriter writer) { 306 int counter = 0; 307 for (Map.Entry<Integer, PackageTagsList> tagEntry : tags.entrySet()) { 308 writer.print(" #"); writer.print(counter++); writer.print(": "); 309 writer.print(tagEntry.getKey().toString()); writer.print("="); 310 tagEntry.getValue().dump(writer); 311 } 312 } 313 314 resolveDatasourceOp(int code, int uid, @NonNull String packageName, @Nullable String attributionTag)315 private int resolveDatasourceOp(int code, int uid, @NonNull String packageName, 316 @Nullable String attributionTag) { 317 code = resolveRecordAudioOp(code, uid); 318 if (attributionTag == null) { 319 return code; 320 } 321 int resolvedCode = resolveLocationOp(code); 322 if (resolvedCode != code) { 323 if (isDatasourceAttributionTag(uid, packageName, attributionTag, 324 mLocationTags)) { 325 return resolvedCode; 326 } 327 } else { 328 resolvedCode = resolveArOp(code); 329 if (resolvedCode != code) { 330 if (isDatasourceAttributionTag(uid, packageName, attributionTag, 331 mActivityRecognitionTags)) { 332 return resolvedCode; 333 } 334 } 335 } 336 return code; 337 } 338 initializeActivityRecognizersTags()339 private void initializeActivityRecognizersTags() { 340 final List<String> activityRecognizers = mRoleManager.getRoleHolders( 341 RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER); 342 final int recognizerCount = activityRecognizers.size(); 343 if (recognizerCount > 0) { 344 for (int i = 0; i < recognizerCount; i++) { 345 final String activityRecognizer = activityRecognizers.get(i); 346 updateActivityRecognizerTags(activityRecognizer); 347 } 348 } else { 349 clearActivityRecognitionTags(); 350 } 351 } 352 clearActivityRecognitionTags()353 private void clearActivityRecognitionTags() { 354 synchronized (mLock) { 355 mActivityRecognitionTags.clear(); 356 } 357 } 358 updateActivityRecognizerTags(@onNull String activityRecognizer)359 private void updateActivityRecognizerTags(@NonNull String activityRecognizer) { 360 final int flags = PackageManager.GET_SERVICES 361 | PackageManager.GET_META_DATA 362 | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS 363 | PackageManager.MATCH_DIRECT_BOOT_AWARE 364 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; 365 366 final Intent intent = new Intent(Intent.ACTION_ACTIVITY_RECOGNIZER); 367 intent.setPackage(activityRecognizer); 368 final ResolveInfo resolvedService = mContext.getPackageManager() 369 .resolveServiceAsUser(intent, flags, UserHandle.USER_SYSTEM); 370 if (resolvedService == null || resolvedService.serviceInfo == null) { 371 Log.w(LOG_TAG, "Service recognizer doesn't handle " 372 + Intent.ACTION_ACTIVITY_RECOGNIZER + ", ignoring!"); 373 return; 374 } 375 final Bundle metaData = resolvedService.serviceInfo.metaData; 376 if (metaData == null) { 377 return; 378 } 379 final String tagsList = metaData.getString(ACTIVITY_RECOGNITION_TAGS); 380 if (!TextUtils.isEmpty(tagsList)) { 381 PackageTagsList packageTagsList = new PackageTagsList.Builder(1).add( 382 resolvedService.serviceInfo.packageName, 383 Arrays.asList(tagsList.split(ACTIVITY_RECOGNITION_TAGS_SEPARATOR))).build(); 384 synchronized (mLock) { 385 updateAllowListedTagsForPackageLocked( 386 UserHandle.getAppId(resolvedService.serviceInfo.applicationInfo.uid), 387 packageTagsList, 388 mActivityRecognitionTags); 389 } 390 } 391 } 392 updateAllowListedTagsForPackageLocked(int appId, PackageTagsList packageTagsList, ConcurrentHashMap<Integer, PackageTagsList> datastore)393 private static void updateAllowListedTagsForPackageLocked(int appId, 394 PackageTagsList packageTagsList, 395 ConcurrentHashMap<Integer, PackageTagsList> datastore) { 396 datastore.put(appId, packageTagsList); 397 } 398 isDatasourceAttributionTag(int uid, @NonNull String packageName, @NonNull String attributionTag, @NonNull Map<Integer, PackageTagsList> mappedOps)399 private static boolean isDatasourceAttributionTag(int uid, @NonNull String packageName, 400 @NonNull String attributionTag, @NonNull Map<Integer, PackageTagsList> mappedOps) { 401 // Only a single lookup from the underlying concurrent data structure 402 final PackageTagsList appIdTags = mappedOps.get(UserHandle.getAppId(uid)); 403 return appIdTags != null && appIdTags.contains(packageName, attributionTag); 404 } 405 resolveLocationOp(int code)406 private static int resolveLocationOp(int code) { 407 switch (code) { 408 case AppOpsManager.OP_FINE_LOCATION: 409 return AppOpsManager.OP_FINE_LOCATION_SOURCE; 410 case AppOpsManager.OP_COARSE_LOCATION: 411 return AppOpsManager.OP_COARSE_LOCATION_SOURCE; 412 } 413 return code; 414 } 415 resolveArOp(int code)416 private static int resolveArOp(int code) { 417 if (code == AppOpsManager.OP_ACTIVITY_RECOGNITION) { 418 return AppOpsManager.OP_ACTIVITY_RECOGNITION_SOURCE; 419 } 420 return code; 421 } 422 resolveRecordAudioOp(int code, int uid)423 private int resolveRecordAudioOp(int code, int uid) { 424 if (code == AppOpsManager.OP_RECORD_AUDIO_HOTWORD) { 425 if (!mIsHotwordDetectionServiceRequired) { 426 return code; 427 } 428 // Only the HotwordDetectionService can use the RECORD_AUDIO_HOTWORD op which doesn't 429 // incur the privacy indicator. Downgrade to standard RECORD_AUDIO for other processes. 430 final HotwordDetectionServiceIdentity hotwordDetectionServiceIdentity = 431 mVoiceInteractionManagerInternal.getHotwordDetectionServiceIdentity(); 432 if (hotwordDetectionServiceIdentity != null 433 && uid == hotwordDetectionServiceIdentity.getIsolatedUid()) { 434 return code; 435 } 436 return AppOpsManager.OP_RECORD_AUDIO; 437 } 438 return code; 439 } 440 resolveUid(int code, int uid)441 private int resolveUid(int code, int uid) { 442 // The HotwordDetectionService is an isolated service, which ordinarily cannot hold 443 // permissions. So we allow it to assume the owning package identity for certain 444 // operations. 445 // Note: The package name coming from the audio server is already the one for the owning 446 // package, so we don't need to modify it. 447 if (Process.isIsolated(uid) // simple check which fails-fast for the common case 448 && (code == AppOpsManager.OP_RECORD_AUDIO 449 || code == AppOpsManager.OP_RECORD_AUDIO_HOTWORD)) { 450 final HotwordDetectionServiceIdentity hotwordDetectionServiceIdentity = 451 mVoiceInteractionManagerInternal.getHotwordDetectionServiceIdentity(); 452 if (hotwordDetectionServiceIdentity != null 453 && uid == hotwordDetectionServiceIdentity.getIsolatedUid()) { 454 uid = hotwordDetectionServiceIdentity.getOwnerUid(); 455 } 456 } 457 return uid; 458 } 459 } 460