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