1 /* 2 * Copyright (C) 2019 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.compat; 18 19 import static android.Manifest.permission.LOG_COMPAT_CHANGE; 20 import static android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG; 21 import static android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD; 22 import static android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG; 23 import static android.content.pm.PackageManager.PERMISSION_GRANTED; 24 import static android.os.Process.SYSTEM_UID; 25 26 import android.annotation.UserIdInt; 27 import android.app.ActivityManager; 28 import android.app.IActivityManager; 29 import android.app.compat.PackageOverride; 30 import android.content.BroadcastReceiver; 31 import android.content.Context; 32 import android.content.Intent; 33 import android.content.IntentFilter; 34 import android.content.pm.ApplicationInfo; 35 import android.content.pm.PackageManagerInternal; 36 import android.net.Uri; 37 import android.os.Binder; 38 import android.os.Build; 39 import android.os.Process; 40 import android.os.RemoteException; 41 import android.os.UserHandle; 42 import android.util.Slog; 43 44 import com.android.internal.annotations.VisibleForTesting; 45 import com.android.internal.compat.AndroidBuildClassifier; 46 import com.android.internal.compat.ChangeReporter; 47 import com.android.internal.compat.CompatibilityChangeConfig; 48 import com.android.internal.compat.CompatibilityChangeInfo; 49 import com.android.internal.compat.CompatibilityOverrideConfig; 50 import com.android.internal.compat.CompatibilityOverridesToRemoveConfig; 51 import com.android.internal.compat.IOverrideValidator; 52 import com.android.internal.compat.IPlatformCompat; 53 import com.android.internal.util.DumpUtils; 54 import com.android.server.LocalServices; 55 56 import java.io.FileDescriptor; 57 import java.io.PrintWriter; 58 import java.util.Arrays; 59 import java.util.Collection; 60 import java.util.HashMap; 61 import java.util.Map; 62 63 /** 64 * System server internal API for gating and reporting compatibility changes. 65 */ 66 public class PlatformCompat extends IPlatformCompat.Stub { 67 68 private static final String TAG = "Compatibility"; 69 70 private final Context mContext; 71 private final ChangeReporter mChangeReporter; 72 private final CompatConfig mCompatConfig; 73 private final AndroidBuildClassifier mBuildClassifier; 74 PlatformCompat(Context context)75 public PlatformCompat(Context context) { 76 mContext = context; 77 mChangeReporter = new ChangeReporter(ChangeReporter.SOURCE_SYSTEM_SERVER); 78 mBuildClassifier = new AndroidBuildClassifier(); 79 mCompatConfig = CompatConfig.create(mBuildClassifier, mContext); 80 } 81 82 @VisibleForTesting PlatformCompat(Context context, CompatConfig compatConfig, AndroidBuildClassifier buildClassifier)83 PlatformCompat(Context context, CompatConfig compatConfig, 84 AndroidBuildClassifier buildClassifier) { 85 mContext = context; 86 mChangeReporter = new ChangeReporter(ChangeReporter.SOURCE_SYSTEM_SERVER); 87 mCompatConfig = compatConfig; 88 mBuildClassifier = buildClassifier; 89 90 registerPackageReceiver(context); 91 } 92 93 @Override reportChange(long changeId, ApplicationInfo appInfo)94 public void reportChange(long changeId, ApplicationInfo appInfo) { 95 checkCompatChangeLogPermission(); 96 reportChangeInternal(changeId, appInfo.uid, ChangeReporter.STATE_LOGGED); 97 } 98 99 @Override reportChangeByPackageName(long changeId, String packageName, @UserIdInt int userId)100 public void reportChangeByPackageName(long changeId, String packageName, 101 @UserIdInt int userId) { 102 checkCompatChangeLogPermission(); 103 ApplicationInfo appInfo = getApplicationInfo(packageName, userId); 104 if (appInfo != null) { 105 reportChangeInternal(changeId, appInfo.uid, ChangeReporter.STATE_LOGGED); 106 } 107 } 108 109 @Override reportChangeByUid(long changeId, int uid)110 public void reportChangeByUid(long changeId, int uid) { 111 checkCompatChangeLogPermission(); 112 reportChangeInternal(changeId, uid, ChangeReporter.STATE_LOGGED); 113 } 114 reportChangeInternal(long changeId, int uid, int state)115 private void reportChangeInternal(long changeId, int uid, int state) { 116 mChangeReporter.reportChange(uid, changeId, state); 117 } 118 119 @Override isChangeEnabled(long changeId, ApplicationInfo appInfo)120 public boolean isChangeEnabled(long changeId, ApplicationInfo appInfo) { 121 checkCompatChangeReadAndLogPermission(); 122 return isChangeEnabledInternal(changeId, appInfo); 123 } 124 125 @Override isChangeEnabledByPackageName(long changeId, String packageName, @UserIdInt int userId)126 public boolean isChangeEnabledByPackageName(long changeId, String packageName, 127 @UserIdInt int userId) { 128 checkCompatChangeReadAndLogPermission(); 129 ApplicationInfo appInfo = getApplicationInfo(packageName, userId); 130 if (appInfo == null) { 131 return mCompatConfig.willChangeBeEnabled(changeId, packageName); 132 } 133 return isChangeEnabledInternal(changeId, appInfo); 134 } 135 136 @Override isChangeEnabledByUid(long changeId, int uid)137 public boolean isChangeEnabledByUid(long changeId, int uid) { 138 checkCompatChangeReadAndLogPermission(); 139 String[] packages = mContext.getPackageManager().getPackagesForUid(uid); 140 if (packages == null || packages.length == 0) { 141 return mCompatConfig.defaultChangeIdValue(changeId); 142 } 143 boolean enabled = true; 144 for (String packageName : packages) { 145 enabled &= isChangeEnabledByPackageName(changeId, packageName, 146 UserHandle.getUserId(uid)); 147 } 148 return enabled; 149 } 150 151 /** 152 * Internal version of the above method, without logging. 153 * 154 * <p>Does not perform costly permission check. 155 * TODO(b/167551701): Remove this method and add 'loggability' as a changeid property. 156 */ isChangeEnabledInternalNoLogging(long changeId, ApplicationInfo appInfo)157 public boolean isChangeEnabledInternalNoLogging(long changeId, ApplicationInfo appInfo) { 158 return mCompatConfig.isChangeEnabled(changeId, appInfo); 159 } 160 161 /** 162 * Internal version of {@link #isChangeEnabled(long, ApplicationInfo)}. 163 * 164 * <p>Does not perform costly permission check. 165 */ isChangeEnabledInternal(long changeId, ApplicationInfo appInfo)166 public boolean isChangeEnabledInternal(long changeId, ApplicationInfo appInfo) { 167 boolean enabled = isChangeEnabledInternalNoLogging(changeId, appInfo); 168 if (appInfo != null) { 169 reportChangeInternal(changeId, appInfo.uid, 170 enabled ? ChangeReporter.STATE_ENABLED : ChangeReporter.STATE_DISABLED); 171 } 172 return enabled; 173 } 174 175 /** 176 * Called by the package manager to check if a given change is enabled for a given package name 177 * and the target sdk version while the package is in the parsing state. 178 * 179 * <p>Does not perform costly permission check. 180 * 181 * @param changeId the ID of the change in question 182 * @param packageName package name to check for 183 * @param targetSdkVersion target sdk version to check for 184 * @return {@code true} if the change would be enabled for this package name. 185 */ isChangeEnabledInternal(long changeId, String packageName, int targetSdkVersion)186 public boolean isChangeEnabledInternal(long changeId, String packageName, 187 int targetSdkVersion) { 188 if (mCompatConfig.willChangeBeEnabled(changeId, packageName)) { 189 final ApplicationInfo appInfo = new ApplicationInfo(); 190 appInfo.packageName = packageName; 191 appInfo.targetSdkVersion = targetSdkVersion; 192 return isChangeEnabledInternalNoLogging(changeId, appInfo); 193 } 194 return false; 195 } 196 197 @Override setOverrides(CompatibilityChangeConfig overrides, String packageName)198 public void setOverrides(CompatibilityChangeConfig overrides, String packageName) { 199 checkCompatChangeOverridePermission(); 200 Map<Long, PackageOverride> overridesMap = new HashMap<>(); 201 for (long change : overrides.enabledChanges()) { 202 overridesMap.put(change, new PackageOverride.Builder().setEnabled(true).build()); 203 } 204 for (long change : overrides.disabledChanges()) { 205 overridesMap.put(change, new PackageOverride.Builder().setEnabled(false) 206 .build()); 207 } 208 mCompatConfig.addPackageOverrides(new CompatibilityOverrideConfig(overridesMap), 209 packageName, /* skipUnknownChangeIds */ false); 210 killPackage(packageName); 211 } 212 213 @Override setOverridesForTest(CompatibilityChangeConfig overrides, String packageName)214 public void setOverridesForTest(CompatibilityChangeConfig overrides, String packageName) { 215 checkCompatChangeOverridePermission(); 216 Map<Long, PackageOverride> overridesMap = new HashMap<>(); 217 for (long change : overrides.enabledChanges()) { 218 overridesMap.put(change, new PackageOverride.Builder().setEnabled(true).build()); 219 } 220 for (long change : overrides.disabledChanges()) { 221 overridesMap.put(change, new PackageOverride.Builder().setEnabled(false) 222 .build()); 223 } 224 mCompatConfig.addPackageOverrides(new CompatibilityOverrideConfig(overridesMap), 225 packageName, /* skipUnknownChangeIds */ false); 226 } 227 228 @Override putOverridesOnReleaseBuilds(CompatibilityOverrideConfig overrides, String packageName)229 public void putOverridesOnReleaseBuilds(CompatibilityOverrideConfig overrides, 230 String packageName) { 231 // TODO(b/183630314): Unify the permission enforcement with the other setOverrides* methods. 232 checkCompatChangeOverrideOverridablePermission(); 233 checkAllCompatOverridesAreOverridable(overrides.overrides.keySet()); 234 mCompatConfig.addPackageOverrides(overrides, packageName, /* skipUnknownChangeIds= */ true); 235 } 236 237 @Override enableTargetSdkChanges(String packageName, int targetSdkVersion)238 public int enableTargetSdkChanges(String packageName, int targetSdkVersion) { 239 checkCompatChangeOverridePermission(); 240 int numChanges = 241 mCompatConfig.enableTargetSdkChangesForPackage(packageName, targetSdkVersion); 242 killPackage(packageName); 243 return numChanges; 244 } 245 246 @Override disableTargetSdkChanges(String packageName, int targetSdkVersion)247 public int disableTargetSdkChanges(String packageName, int targetSdkVersion) { 248 checkCompatChangeOverridePermission(); 249 int numChanges = 250 mCompatConfig.disableTargetSdkChangesForPackage(packageName, targetSdkVersion); 251 killPackage(packageName); 252 return numChanges; 253 } 254 255 @Override clearOverrides(String packageName)256 public void clearOverrides(String packageName) { 257 checkCompatChangeOverridePermission(); 258 mCompatConfig.removePackageOverrides(packageName); 259 killPackage(packageName); 260 } 261 262 @Override clearOverridesForTest(String packageName)263 public void clearOverridesForTest(String packageName) { 264 checkCompatChangeOverridePermission(); 265 mCompatConfig.removePackageOverrides(packageName); 266 } 267 268 @Override clearOverride(long changeId, String packageName)269 public boolean clearOverride(long changeId, String packageName) { 270 checkCompatChangeOverridePermission(); 271 boolean existed = mCompatConfig.removeOverride(changeId, packageName); 272 killPackage(packageName); 273 return existed; 274 } 275 276 @Override clearOverrideForTest(long changeId, String packageName)277 public boolean clearOverrideForTest(long changeId, String packageName) { 278 checkCompatChangeOverridePermission(); 279 return mCompatConfig.removeOverride(changeId, packageName); 280 } 281 282 @Override removeOverridesOnReleaseBuilds( CompatibilityOverridesToRemoveConfig overridesToRemove, String packageName)283 public void removeOverridesOnReleaseBuilds( 284 CompatibilityOverridesToRemoveConfig overridesToRemove, 285 String packageName) { 286 // TODO(b/183630314): Unify the permission enforcement with the other setOverrides* methods. 287 checkCompatChangeOverrideOverridablePermission(); 288 checkAllCompatOverridesAreOverridable(overridesToRemove.changeIds); 289 mCompatConfig.removePackageOverrides(overridesToRemove, packageName); 290 } 291 292 @Override getAppConfig(ApplicationInfo appInfo)293 public CompatibilityChangeConfig getAppConfig(ApplicationInfo appInfo) { 294 checkCompatChangeReadAndLogPermission(); 295 return mCompatConfig.getAppConfig(appInfo); 296 } 297 298 @Override listAllChanges()299 public CompatibilityChangeInfo[] listAllChanges() { 300 checkCompatChangeReadPermission(); 301 return mCompatConfig.dumpChanges(); 302 } 303 304 @Override listUIChanges()305 public CompatibilityChangeInfo[] listUIChanges() { 306 return Arrays.stream(listAllChanges()).filter(this::isShownInUI).toArray( 307 CompatibilityChangeInfo[]::new); 308 } 309 310 /** Checks whether the change is known to the compat config. */ isKnownChangeId(long changeId)311 public boolean isKnownChangeId(long changeId) { 312 return mCompatConfig.isKnownChangeId(changeId); 313 } 314 315 /** 316 * Retrieves the set of disabled changes for a given app. Any change ID not in the returned 317 * array is by default enabled for the app. 318 * 319 * @param appInfo The app in question 320 * @return A sorted long array of change IDs. We use a primitive array to minimize memory 321 * footprint: Every app process will store this array statically so we aim to reduce 322 * overhead as much as possible. 323 */ getDisabledChanges(ApplicationInfo appInfo)324 public long[] getDisabledChanges(ApplicationInfo appInfo) { 325 return mCompatConfig.getDisabledChanges(appInfo); 326 } 327 328 /** 329 * Look up a change ID by name. 330 * 331 * @param name Name of the change to look up 332 * @return The change ID, or {@code -1} if no change with that name exists. 333 */ lookupChangeId(String name)334 public long lookupChangeId(String name) { 335 return mCompatConfig.lookupChangeId(name); 336 } 337 338 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)339 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 340 if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, "platform_compat", pw)) { 341 return; 342 } 343 checkCompatChangeReadAndLogPermission(); 344 mCompatConfig.dumpConfig(pw); 345 } 346 347 @Override getOverrideValidator()348 public IOverrideValidator getOverrideValidator() { 349 return mCompatConfig.getOverrideValidator(); 350 } 351 352 /** 353 * Clears information stored about events reported on behalf of an app. 354 * 355 * <p>To be called once upon app start or end. A second call would be a no-op. 356 * 357 * @param appInfo the app to reset 358 */ resetReporting(ApplicationInfo appInfo)359 public void resetReporting(ApplicationInfo appInfo) { 360 mChangeReporter.resetReportedChanges(appInfo.uid); 361 } 362 getApplicationInfo(String packageName, int userId)363 private ApplicationInfo getApplicationInfo(String packageName, int userId) { 364 return LocalServices.getService(PackageManagerInternal.class).getApplicationInfo( 365 packageName, 0, Process.myUid(), userId); 366 } 367 killPackage(String packageName)368 private void killPackage(String packageName) { 369 int uid = LocalServices.getService(PackageManagerInternal.class).getPackageUid(packageName, 370 0, UserHandle.myUserId()); 371 372 if (uid < 0) { 373 Slog.w(TAG, "Didn't find package " + packageName + " on device."); 374 return; 375 } 376 377 Slog.d(TAG, "Killing package " + packageName + " (UID " + uid + ")."); 378 killUid(UserHandle.getAppId(uid)); 379 } 380 killUid(int appId)381 private void killUid(int appId) { 382 final long identity = Binder.clearCallingIdentity(); 383 try { 384 IActivityManager am = ActivityManager.getService(); 385 if (am != null) { 386 am.killUid(appId, UserHandle.USER_ALL, "PlatformCompat overrides"); 387 } 388 } catch (RemoteException e) { 389 /* ignore - same process */ 390 } finally { 391 Binder.restoreCallingIdentity(identity); 392 } 393 } 394 checkCompatChangeLogPermission()395 private void checkCompatChangeLogPermission() throws SecurityException { 396 // Don't check for permissions within the system process 397 if (Binder.getCallingUid() == SYSTEM_UID) { 398 return; 399 } 400 if (mContext.checkCallingOrSelfPermission(LOG_COMPAT_CHANGE) != PERMISSION_GRANTED) { 401 throw new SecurityException("Cannot log compat change usage"); 402 } 403 } 404 checkCompatChangeReadPermission()405 private void checkCompatChangeReadPermission() { 406 // Don't check for permissions within the system process 407 if (Binder.getCallingUid() == SYSTEM_UID) { 408 return; 409 } 410 if (mContext.checkCallingOrSelfPermission(READ_COMPAT_CHANGE_CONFIG) 411 != PERMISSION_GRANTED) { 412 throw new SecurityException("Cannot read compat change"); 413 } 414 } 415 checkCompatChangeOverridePermission()416 private void checkCompatChangeOverridePermission() { 417 // Don't check for permissions within the system process 418 if (Binder.getCallingUid() == SYSTEM_UID) { 419 return; 420 } 421 if (mContext.checkCallingOrSelfPermission(OVERRIDE_COMPAT_CHANGE_CONFIG) 422 != PERMISSION_GRANTED) { 423 throw new SecurityException("Cannot override compat change"); 424 } 425 } 426 checkCompatChangeOverrideOverridablePermission()427 private void checkCompatChangeOverrideOverridablePermission() { 428 // Don't check for permissions within the system process 429 if (Binder.getCallingUid() == SYSTEM_UID) { 430 return; 431 } 432 if (mContext.checkCallingOrSelfPermission(OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD) 433 != PERMISSION_GRANTED) { 434 throw new SecurityException("Cannot override compat change"); 435 } 436 } 437 checkAllCompatOverridesAreOverridable(Collection<Long> changeIds)438 private void checkAllCompatOverridesAreOverridable(Collection<Long> changeIds) { 439 for (Long changeId : changeIds) { 440 if (isKnownChangeId(changeId) && !mCompatConfig.isOverridable(changeId)) { 441 throw new SecurityException("Only change ids marked as Overridable can be " 442 + "overridden."); 443 } 444 } 445 } 446 checkCompatChangeReadAndLogPermission()447 private void checkCompatChangeReadAndLogPermission() { 448 checkCompatChangeReadPermission(); 449 checkCompatChangeLogPermission(); 450 } 451 isShownInUI(CompatibilityChangeInfo change)452 private boolean isShownInUI(CompatibilityChangeInfo change) { 453 if (change.getLoggingOnly()) { 454 return false; 455 } 456 if (change.getId() == CompatChange.CTS_SYSTEM_API_CHANGEID) { 457 return false; 458 } 459 if (change.getEnableSinceTargetSdk() > 0) { 460 return change.getEnableSinceTargetSdk() >= Build.VERSION_CODES.Q 461 && change.getEnableSinceTargetSdk() <= mBuildClassifier.platformTargetSdk(); 462 } 463 return true; 464 } 465 466 /** 467 * Registers a listener for change state overrides. 468 * 469 * <p>Only one listener per change is allowed. 470 * 471 * <p>{@code listener.onCompatChange(String)} method is guaranteed to be called with 472 * packageName before the app is killed upon an override change. The state of a change is not 473 * guaranteed to change when {@code listener.onCompatChange(String)} is called. 474 * 475 * @param changeId to get updates for 476 * @param listener the listener that will be called upon a potential change for package 477 * @return {@code true} if a change with changeId was already known, or (@code false} 478 * otherwise 479 * @throws IllegalStateException if a listener was already registered for changeId 480 */ registerListener(long changeId, CompatChange.ChangeListener listener)481 public boolean registerListener(long changeId, CompatChange.ChangeListener listener) { 482 return mCompatConfig.registerListener(changeId, listener); 483 } 484 485 /** 486 * Registers a broadcast receiver that listens for package install, replace or remove. 487 * 488 * @param context the context where the receiver should be registered 489 */ registerPackageReceiver(Context context)490 public void registerPackageReceiver(Context context) { 491 final BroadcastReceiver receiver = new BroadcastReceiver() { 492 @Override 493 public void onReceive(Context context, Intent intent) { 494 if (intent == null) { 495 return; 496 } 497 final Uri packageData = intent.getData(); 498 if (packageData == null) { 499 return; 500 } 501 final String packageName = packageData.getSchemeSpecificPart(); 502 if (packageName == null) { 503 return; 504 } 505 mCompatConfig.recheckOverrides(packageName); 506 } 507 }; 508 IntentFilter filter = new IntentFilter(); 509 filter.addAction(Intent.ACTION_PACKAGE_ADDED); 510 filter.addAction(Intent.ACTION_PACKAGE_REPLACED); 511 filter.addAction(Intent.ACTION_PACKAGE_REMOVED); 512 filter.addDataScheme("package"); 513 context.registerReceiverForAllUsers(receiver, filter, /* broadcastPermission= */ 514 null, /* scheduler= */ null); 515 } 516 517 /** 518 * Registers the observer for 519 * {@link android.provider.Settings.Global#FORCE_NON_DEBUGGABLE_FINAL_BUILD_FOR_COMPAT}. 520 */ registerContentObserver()521 public void registerContentObserver() { 522 mCompatConfig.registerContentObserver(); 523 } 524 } 525