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