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