• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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