1 /*
2  * Copyright (C) 2024 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.ecm;
18 
19 import android.Manifest;
20 import android.annotation.FlaggedApi;
21 import android.annotation.IntDef;
22 import android.annotation.UserIdInt;
23 import android.app.AppOpsManager;
24 import android.app.ecm.EnhancedConfirmationManager;
25 import android.app.ecm.IEnhancedConfirmationManager;
26 import android.app.role.RoleManager;
27 import android.content.Context;
28 import android.content.pm.ApplicationInfo;
29 import android.content.pm.InstallSourceInfo;
30 import android.content.pm.PackageInstaller;
31 import android.content.pm.PackageManager;
32 import android.content.pm.PackageManager.NameNotFoundException;
33 import android.content.pm.SignedPackage;
34 import android.os.Binder;
35 import android.os.Build;
36 import android.os.SystemConfigManager;
37 import android.os.UserHandle;
38 import android.permission.flags.Flags;
39 import android.util.ArrayMap;
40 import android.util.ArraySet;
41 import android.util.Log;
42 
43 import androidx.annotation.Keep;
44 import androidx.annotation.NonNull;
45 import androidx.annotation.Nullable;
46 import androidx.annotation.RequiresApi;
47 
48 import com.android.internal.util.Preconditions;
49 import com.android.permission.util.UserUtils;
50 import com.android.server.SystemService;
51 
52 import java.lang.annotation.Retention;
53 import java.util.ArrayList;
54 import java.util.List;
55 import java.util.Map;
56 import java.util.Set;
57 
58 
59 /**
60  * Service for ECM (Enhanced Confirmation Mode).
61  *
62  * @see EnhancedConfirmationManager
63  *
64  * @hide
65  */
66 @Keep
67 @FlaggedApi(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
68 @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
69 public class EnhancedConfirmationService extends SystemService {
70     private static final String LOG_TAG = EnhancedConfirmationService.class.getSimpleName();
71 
72     private Map<String, List<byte[]>> mTrustedPackageCertDigests;
73     private Map<String, List<byte[]>> mTrustedInstallerCertDigests;
74 
EnhancedConfirmationService(@onNull Context context)75     public EnhancedConfirmationService(@NonNull Context context) {
76         super(context);
77     }
78 
79     @Override
onStart()80     public void onStart() {
81         Context context = getContext();
82         SystemConfigManager systemConfigManager = context.getSystemService(
83                 SystemConfigManager.class);
84         mTrustedPackageCertDigests = toTrustedPackageMap(
85                 systemConfigManager.getEnhancedConfirmationTrustedPackages());
86         mTrustedInstallerCertDigests = toTrustedPackageMap(
87                 systemConfigManager.getEnhancedConfirmationTrustedInstallers());
88 
89         publishBinderService(Context.ECM_ENHANCED_CONFIRMATION_SERVICE, new Stub());
90     }
91 
toTrustedPackageMap(Set<SignedPackage> signedPackages)92     private Map<String, List<byte[]>> toTrustedPackageMap(Set<SignedPackage> signedPackages) {
93         ArrayMap<String, List<byte[]>> trustedPackageMap = new ArrayMap<>();
94         for (SignedPackage signedPackage : signedPackages) {
95             ArrayList<byte[]> certDigests = (ArrayList<byte[]>) trustedPackageMap.computeIfAbsent(
96                     signedPackage.getPackageName(), packageName -> new ArrayList<>(1));
97             certDigests.add(signedPackage.getCertificateDigest());
98         }
99         return trustedPackageMap;
100     }
101 
102     private class Stub extends IEnhancedConfirmationManager.Stub {
103 
104         /** A map of ECM states to their corresponding app op states */
105         @Retention(java.lang.annotation.RetentionPolicy.SOURCE)
106         @IntDef(prefix = {"ECM_STATE_"}, value = {EcmState.ECM_STATE_NOT_GUARDED,
107                 EcmState.ECM_STATE_GUARDED, EcmState.ECM_STATE_GUARDED_AND_ACKNOWLEDGED,
108                 EcmState.ECM_STATE_IMPLICIT})
109         private @interface EcmState {
110             int ECM_STATE_NOT_GUARDED = AppOpsManager.MODE_ALLOWED;
111             int ECM_STATE_GUARDED = AppOpsManager.MODE_ERRORED;
112             int ECM_STATE_GUARDED_AND_ACKNOWLEDGED = AppOpsManager.MODE_IGNORED;
113             int ECM_STATE_IMPLICIT = AppOpsManager.MODE_DEFAULT;
114         }
115 
116         private static final ArraySet<String> PROTECTED_SETTINGS = new ArraySet<>();
117 
118         static {
119             // Runtime permissions
120             PROTECTED_SETTINGS.add(Manifest.permission.SEND_SMS);
121             PROTECTED_SETTINGS.add(Manifest.permission.RECEIVE_SMS);
122             PROTECTED_SETTINGS.add(Manifest.permission.READ_SMS);
123             PROTECTED_SETTINGS.add(Manifest.permission.RECEIVE_MMS);
124             PROTECTED_SETTINGS.add(Manifest.permission.RECEIVE_WAP_PUSH);
125             PROTECTED_SETTINGS.add(Manifest.permission.READ_CELL_BROADCASTS);
126             PROTECTED_SETTINGS.add(Manifest.permission_group.SMS);
127 
128             PROTECTED_SETTINGS.add(Manifest.permission.BIND_DEVICE_ADMIN);
129             // App ops
130             PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE);
131             PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS);
132             PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW);
133             PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_GET_USAGE_STATS);
134             PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_LOADER_USAGE_STATS);
135             // Default application roles.
136             PROTECTED_SETTINGS.add(RoleManager.ROLE_DIALER);
137             PROTECTED_SETTINGS.add(RoleManager.ROLE_SMS);
138         }
139 
140         private final @NonNull Context mContext;
141         private final String mAttributionTag;
142         private final AppOpsManager mAppOpsManager;
143         private final PackageManager mPackageManager;
144 
Stub()145         Stub() {
146             Context context = getContext();
147             mContext = context;
148             mAttributionTag = context.getAttributionTag();
149             mAppOpsManager = context.getSystemService(AppOpsManager.class);
150             mPackageManager = context.getPackageManager();
151         }
152 
isRestricted(@onNull String packageName, @NonNull String settingIdentifier, @UserIdInt int userId)153         public boolean isRestricted(@NonNull String packageName, @NonNull String settingIdentifier,
154                 @UserIdInt int userId) {
155             enforcePermissions("isRestricted", userId);
156             if (!UserUtils.isUserExistent(userId, getContext())) {
157                 Log.e(LOG_TAG, "user " + userId + " does not exist");
158                 return false;
159             }
160 
161             Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
162             Preconditions.checkStringNotEmpty(settingIdentifier,
163                     "settingIdentifier cannot be null or empty");
164 
165             try {
166                 return isSettingEcmProtected(settingIdentifier) && isPackageEcmGuarded(packageName,
167                         userId);
168             } catch (NameNotFoundException e) {
169                 throw new IllegalArgumentException(e);
170             }
171         }
172 
clearRestriction(@onNull String packageName, @UserIdInt int userId)173         public void clearRestriction(@NonNull String packageName, @UserIdInt int userId) {
174             enforcePermissions("clearRestriction", userId);
175             if (!UserUtils.isUserExistent(userId, getContext())) {
176                 return;
177             }
178 
179             Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
180 
181             try {
182                 int state = getAppEcmState(packageName, userId);
183                 boolean isAllowed = state == EcmState.ECM_STATE_GUARDED_AND_ACKNOWLEDGED;
184                 if (!isAllowed) {
185                     throw new IllegalStateException("Clear restriction attempted but not allowed");
186                 }
187                 setAppEcmState(packageName, EcmState.ECM_STATE_NOT_GUARDED, userId);
188                 EnhancedConfirmationStatsLogUtils.INSTANCE.logRestrictionCleared(
189                         getPackageUid(packageName, userId));
190             } catch (NameNotFoundException e) {
191                 throw new IllegalArgumentException(e);
192             }
193         }
194 
isClearRestrictionAllowed(@onNull String packageName, @UserIdInt int userId)195         public boolean isClearRestrictionAllowed(@NonNull String packageName,
196                 @UserIdInt int userId) {
197             enforcePermissions("isClearRestrictionAllowed", userId);
198             if (!UserUtils.isUserExistent(userId, getContext())) {
199                 return false;
200             }
201 
202             Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
203 
204             try {
205                 int state = getAppEcmState(packageName, userId);
206                 return state == EcmState.ECM_STATE_GUARDED_AND_ACKNOWLEDGED;
207             } catch (NameNotFoundException e) {
208                 throw new IllegalArgumentException(e);
209             }
210         }
211 
setClearRestrictionAllowed(@onNull String packageName, @UserIdInt int userId)212         public void setClearRestrictionAllowed(@NonNull String packageName, @UserIdInt int userId) {
213             enforcePermissions("setClearRestrictionAllowed", userId);
214             if (!UserUtils.isUserExistent(userId, getContext())) {
215                 return;
216             }
217 
218             Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
219 
220             try {
221                 if (isPackageEcmGuarded(packageName, userId)) {
222                     setAppEcmState(packageName, EcmState.ECM_STATE_GUARDED_AND_ACKNOWLEDGED,
223                             userId);
224                 }
225             } catch (NameNotFoundException e) {
226                 throw new IllegalArgumentException(e);
227             }
228         }
229 
enforcePermissions(@onNull String methodName, @UserIdInt int userId)230         private void enforcePermissions(@NonNull String methodName, @UserIdInt int userId) {
231             UserUtils.enforceCrossUserPermission(userId, false, methodName, mContext);
232             mContext.enforceCallingOrSelfPermission(
233                     android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES, methodName);
234         }
235 
isPackageEcmGuarded(@onNull String packageName, @UserIdInt int userId)236         private boolean isPackageEcmGuarded(@NonNull String packageName, @UserIdInt int userId)
237                 throws NameNotFoundException {
238             ApplicationInfo applicationInfo = getApplicationInfoAsUser(packageName, userId);
239             // Always trust allow-listed and pre-installed packages
240             if (isAllowlistedPackage(packageName) || isAllowlistedInstaller(packageName)
241                     || isPackagePreinstalled(applicationInfo)) {
242                 return false;
243             }
244 
245             // If the package already has an explicitly-set state, use that
246             @EcmState int ecmState = getAppEcmState(packageName, userId);
247             if (ecmState == EcmState.ECM_STATE_GUARDED
248                     || ecmState == EcmState.ECM_STATE_GUARDED_AND_ACKNOWLEDGED) {
249                 return true;
250             }
251             if (ecmState == EcmState.ECM_STATE_NOT_GUARDED) {
252                 return false;
253             }
254 
255             // Otherwise, lazily decide whether the app is considered guarded.
256             InstallSourceInfo installSource;
257             try {
258                 installSource = mContext.createContextAsUser(UserHandle.of(userId), 0)
259                         .getPackageManager()
260                         .getInstallSourceInfo(packageName);
261             } catch (NameNotFoundException e) {
262                 Log.w(LOG_TAG, "Package not found: " + packageName);
263                 return false;
264             }
265 
266             // These install sources are always considered dangerous.
267             // PackageInstallers that are trusted can use these as a signal that the
268             // packages they've installed aren't as trusted as themselves.
269             int packageSource = installSource.getPackageSource();
270             if (packageSource == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE
271                     || packageSource == PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE) {
272                 return true;
273             }
274             String installingPackageName = installSource.getInstallingPackageName();
275             ApplicationInfo installingApplicationInfo =
276                     getApplicationInfoAsUser(installingPackageName, userId);
277 
278             // ECM doesn't consider a transitive chain of trust for install sources.
279             // If this package hasn't been explicitly handled by this point
280             // then it is exempt from ECM if the immediate parent is a trusted installer
281             return !(trustPackagesInstalledViaNonAllowlistedInstallers()
282                     || isPackagePreinstalled(installingApplicationInfo)
283                     || isAllowlistedInstaller(installingPackageName));
284         }
285 
isAllowlistedPackage(String packageName)286         private boolean isAllowlistedPackage(String packageName) {
287             return isPackageSignedWithAnyOf(packageName,
288                     mTrustedPackageCertDigests.get(packageName));
289         }
290 
isAllowlistedInstaller(String packageName)291         private boolean isAllowlistedInstaller(String packageName) {
292             return isPackageSignedWithAnyOf(packageName,
293                     mTrustedInstallerCertDigests.get(packageName));
294         }
295 
isPackageSignedWithAnyOf(String packageName, List<byte[]> certDigests)296         private boolean isPackageSignedWithAnyOf(String packageName, List<byte[]> certDigests) {
297             if (packageName != null && certDigests != null) {
298                 for (int i = 0, count = certDigests.size(); i < count; i++) {
299                     byte[] trustedCertDigest = certDigests.get(i);
300                     if (mPackageManager.hasSigningCertificate(packageName, trustedCertDigest,
301                             PackageManager.CERT_INPUT_SHA256)) {
302                         return true;
303                     }
304                 }
305             }
306             return false;
307         }
308 
309         /**
310          * @return {@code true} if zero {@code <enhanced-confirmation-trusted-installer>} entries
311          * are defined in {@code frameworks/base/data/etc/enhanced-confirmation.xml}; in this case,
312          * we treat all installers as trusted.
313          */
trustPackagesInstalledViaNonAllowlistedInstallers()314         private boolean trustPackagesInstalledViaNonAllowlistedInstallers() {
315             return mTrustedInstallerCertDigests.isEmpty();
316         }
317 
isPackagePreinstalled(@ullable ApplicationInfo applicationInfo)318         private boolean isPackagePreinstalled(@Nullable ApplicationInfo applicationInfo) {
319             if (applicationInfo == null) {
320                 return false;
321             }
322             return (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
323         }
324 
setAppEcmState(@onNull String packageName, @EcmState int ecmState, @UserIdInt int userId)325         private void setAppEcmState(@NonNull String packageName, @EcmState int ecmState,
326                 @UserIdInt int userId) throws NameNotFoundException {
327             int packageUid = getPackageUid(packageName, userId);
328             final long identityToken = Binder.clearCallingIdentity();
329             try {
330                 mAppOpsManager.setMode(AppOpsManager.OPSTR_ACCESS_RESTRICTED_SETTINGS, packageUid,
331                         packageName, ecmState);
332             } finally {
333                 Binder.restoreCallingIdentity(identityToken);
334             }
335         }
336 
getAppEcmState(@onNull String packageName, @UserIdInt int userId)337         private @EcmState int getAppEcmState(@NonNull String packageName, @UserIdInt int userId)
338                 throws NameNotFoundException {
339             int packageUid = getPackageUid(packageName, userId);
340             final long identityToken = Binder.clearCallingIdentity();
341             try {
342                 return mAppOpsManager.noteOpNoThrow(AppOpsManager.OPSTR_ACCESS_RESTRICTED_SETTINGS,
343                         packageUid, packageName, mAttributionTag, /* message */ null);
344             } finally {
345                 Binder.restoreCallingIdentity(identityToken);
346             }
347         }
348 
isSettingEcmProtected(@onNull String settingIdentifier)349         private boolean isSettingEcmProtected(@NonNull String settingIdentifier) {
350             if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
351                     || mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)
352                     || mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
353                 return false;
354             }
355 
356             if (PROTECTED_SETTINGS.contains(settingIdentifier)) {
357                 return true;
358             }
359             // TODO(b/310218979): Add role selections as protected settings
360             return false;
361         }
362 
363         @Nullable
getApplicationInfoAsUser(@ullable String packageName, @UserIdInt int userId)364         private ApplicationInfo getApplicationInfoAsUser(@Nullable String packageName,
365                 @UserIdInt int userId) {
366             if (packageName == null) {
367                 Log.w(LOG_TAG, "The packageName should not be null.");
368                 return null;
369             }
370             try {
371                 return mPackageManager.getApplicationInfoAsUser(packageName, /* flags */ 0,
372                         UserHandle.of(userId));
373             } catch (NameNotFoundException e) {
374                 Log.w(LOG_TAG, "Package not found: " + packageName, e);
375                 return null;
376             }
377         }
378 
getPackageUid(@onNull String packageName, @UserIdInt int userId)379         private int getPackageUid(@NonNull String packageName, @UserIdInt int userId)
380                 throws NameNotFoundException {
381             return mPackageManager.getApplicationInfoAsUser(packageName, /* flags */ 0,
382                     UserHandle.of(userId)).uid;
383         }
384     }
385 }
386