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.addOverrides(new CompatibilityOverrideConfig(overridesMap), packageName); 209 killPackage(packageName); 210 } 211 212 @Override setOverridesForTest(CompatibilityChangeConfig overrides, String packageName)213 public void setOverridesForTest(CompatibilityChangeConfig overrides, String packageName) { 214 checkCompatChangeOverridePermission(); 215 Map<Long, PackageOverride> overridesMap = new HashMap<>(); 216 for (long change : overrides.enabledChanges()) { 217 overridesMap.put(change, new PackageOverride.Builder().setEnabled(true).build()); 218 } 219 for (long change : overrides.disabledChanges()) { 220 overridesMap.put(change, new PackageOverride.Builder().setEnabled(false) 221 .build()); 222 } 223 mCompatConfig.addOverrides(new CompatibilityOverrideConfig(overridesMap), packageName); 224 } 225 226 @Override putOverridesOnReleaseBuilds(CompatibilityOverrideConfig overrides, String packageName)227 public void putOverridesOnReleaseBuilds(CompatibilityOverrideConfig overrides, 228 String packageName) { 229 // TODO(b/183630314): Unify the permission enforcement with the other setOverrides* methods. 230 checkCompatChangeOverrideOverridablePermission(); 231 checkAllCompatOverridesAreOverridable(overrides.overrides.keySet()); 232 mCompatConfig.addOverrides(overrides, packageName); 233 } 234 235 @Override enableTargetSdkChanges(String packageName, int targetSdkVersion)236 public int enableTargetSdkChanges(String packageName, int targetSdkVersion) { 237 checkCompatChangeOverridePermission(); 238 int numChanges = 239 mCompatConfig.enableTargetSdkChangesForPackage(packageName, targetSdkVersion); 240 killPackage(packageName); 241 return numChanges; 242 } 243 244 @Override disableTargetSdkChanges(String packageName, int targetSdkVersion)245 public int disableTargetSdkChanges(String packageName, int targetSdkVersion) { 246 checkCompatChangeOverridePermission(); 247 int numChanges = 248 mCompatConfig.disableTargetSdkChangesForPackage(packageName, targetSdkVersion); 249 killPackage(packageName); 250 return numChanges; 251 } 252 253 @Override clearOverrides(String packageName)254 public void clearOverrides(String packageName) { 255 checkCompatChangeOverridePermission(); 256 mCompatConfig.removePackageOverrides(packageName); 257 killPackage(packageName); 258 } 259 260 @Override clearOverridesForTest(String packageName)261 public void clearOverridesForTest(String packageName) { 262 checkCompatChangeOverridePermission(); 263 mCompatConfig.removePackageOverrides(packageName); 264 } 265 266 @Override clearOverride(long changeId, String packageName)267 public boolean clearOverride(long changeId, String packageName) { 268 checkCompatChangeOverridePermission(); 269 boolean existed = mCompatConfig.removeOverride(changeId, packageName); 270 killPackage(packageName); 271 return existed; 272 } 273 274 @Override clearOverrideForTest(long changeId, String packageName)275 public boolean clearOverrideForTest(long changeId, String packageName) { 276 checkCompatChangeOverridePermission(); 277 return mCompatConfig.removeOverride(changeId, packageName); 278 } 279 280 @Override removeOverridesOnReleaseBuilds( CompatibilityOverridesToRemoveConfig overridesToRemove, String packageName)281 public void removeOverridesOnReleaseBuilds( 282 CompatibilityOverridesToRemoveConfig overridesToRemove, 283 String packageName) { 284 // TODO(b/183630314): Unify the permission enforcement with the other setOverrides* methods. 285 checkCompatChangeOverrideOverridablePermission(); 286 checkAllCompatOverridesAreOverridable(overridesToRemove.changeIds); 287 mCompatConfig.removePackageOverrides(overridesToRemove, packageName); 288 } 289 290 @Override getAppConfig(ApplicationInfo appInfo)291 public CompatibilityChangeConfig getAppConfig(ApplicationInfo appInfo) { 292 checkCompatChangeReadAndLogPermission(); 293 return mCompatConfig.getAppConfig(appInfo); 294 } 295 296 @Override listAllChanges()297 public CompatibilityChangeInfo[] listAllChanges() { 298 checkCompatChangeReadPermission(); 299 return mCompatConfig.dumpChanges(); 300 } 301 302 @Override listUIChanges()303 public CompatibilityChangeInfo[] listUIChanges() { 304 return Arrays.stream(listAllChanges()).filter(this::isShownInUI).toArray( 305 CompatibilityChangeInfo[]::new); 306 } 307 308 /** Checks whether the change is known to the compat config. */ isKnownChangeId(long changeId)309 public boolean isKnownChangeId(long changeId) { 310 return mCompatConfig.isKnownChangeId(changeId); 311 } 312 313 /** 314 * Retrieves the set of disabled changes for a given app. Any change ID not in the returned 315 * array is by default enabled for the app. 316 * 317 * @param appInfo The app in question 318 * @return A sorted long array of change IDs. We use a primitive array to minimize memory 319 * footprint: Every app process will store this array statically so we aim to reduce 320 * overhead as much as possible. 321 */ getDisabledChanges(ApplicationInfo appInfo)322 public long[] getDisabledChanges(ApplicationInfo appInfo) { 323 return mCompatConfig.getDisabledChanges(appInfo); 324 } 325 326 /** 327 * Look up a change ID by name. 328 * 329 * @param name Name of the change to look up 330 * @return The change ID, or {@code -1} if no change with that name exists. 331 */ lookupChangeId(String name)332 public long lookupChangeId(String name) { 333 return mCompatConfig.lookupChangeId(name); 334 } 335 336 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)337 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 338 if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, "platform_compat", pw)) { 339 return; 340 } 341 checkCompatChangeReadAndLogPermission(); 342 mCompatConfig.dumpConfig(pw); 343 } 344 345 @Override getOverrideValidator()346 public IOverrideValidator getOverrideValidator() { 347 return mCompatConfig.getOverrideValidator(); 348 } 349 350 /** 351 * Clears information stored about events reported on behalf of an app. 352 * 353 * <p>To be called once upon app start or end. A second call would be a no-op. 354 * 355 * @param appInfo the app to reset 356 */ resetReporting(ApplicationInfo appInfo)357 public void resetReporting(ApplicationInfo appInfo) { 358 mChangeReporter.resetReportedChanges(appInfo.uid); 359 } 360 getApplicationInfo(String packageName, int userId)361 private ApplicationInfo getApplicationInfo(String packageName, int userId) { 362 return LocalServices.getService(PackageManagerInternal.class).getApplicationInfo( 363 packageName, 0, Process.myUid(), userId); 364 } 365 killPackage(String packageName)366 private void killPackage(String packageName) { 367 int uid = LocalServices.getService(PackageManagerInternal.class).getPackageUid(packageName, 368 0, UserHandle.myUserId()); 369 370 if (uid < 0) { 371 Slog.w(TAG, "Didn't find package " + packageName + " on device."); 372 return; 373 } 374 375 Slog.d(TAG, "Killing package " + packageName + " (UID " + uid + ")."); 376 killUid(UserHandle.getAppId(uid)); 377 } 378 killUid(int appId)379 private void killUid(int appId) { 380 final long identity = Binder.clearCallingIdentity(); 381 try { 382 IActivityManager am = ActivityManager.getService(); 383 if (am != null) { 384 am.killUid(appId, UserHandle.USER_ALL, "PlatformCompat overrides"); 385 } 386 } catch (RemoteException e) { 387 /* ignore - same process */ 388 } finally { 389 Binder.restoreCallingIdentity(identity); 390 } 391 } 392 checkCompatChangeLogPermission()393 private void checkCompatChangeLogPermission() throws SecurityException { 394 // Don't check for permissions within the system process 395 if (Binder.getCallingUid() == SYSTEM_UID) { 396 return; 397 } 398 if (mContext.checkCallingOrSelfPermission(LOG_COMPAT_CHANGE) != PERMISSION_GRANTED) { 399 throw new SecurityException("Cannot log compat change usage"); 400 } 401 } 402 checkCompatChangeReadPermission()403 private void checkCompatChangeReadPermission() { 404 // Don't check for permissions within the system process 405 if (Binder.getCallingUid() == SYSTEM_UID) { 406 return; 407 } 408 if (mContext.checkCallingOrSelfPermission(READ_COMPAT_CHANGE_CONFIG) 409 != PERMISSION_GRANTED) { 410 throw new SecurityException("Cannot read compat change"); 411 } 412 } 413 checkCompatChangeOverridePermission()414 private void checkCompatChangeOverridePermission() { 415 // Don't check for permissions within the system process 416 if (Binder.getCallingUid() == SYSTEM_UID) { 417 return; 418 } 419 if (mContext.checkCallingOrSelfPermission(OVERRIDE_COMPAT_CHANGE_CONFIG) 420 != PERMISSION_GRANTED) { 421 throw new SecurityException("Cannot override compat change"); 422 } 423 } 424 checkCompatChangeOverrideOverridablePermission()425 private void checkCompatChangeOverrideOverridablePermission() { 426 // Don't check for permissions within the system process 427 if (Binder.getCallingUid() == SYSTEM_UID) { 428 return; 429 } 430 if (mContext.checkCallingOrSelfPermission(OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD) 431 != PERMISSION_GRANTED) { 432 throw new SecurityException("Cannot override compat change"); 433 } 434 } 435 checkAllCompatOverridesAreOverridable(Collection<Long> changeIds)436 private void checkAllCompatOverridesAreOverridable(Collection<Long> changeIds) { 437 for (Long changeId : changeIds) { 438 if (!mCompatConfig.isOverridable(changeId)) { 439 throw new SecurityException("Only change ids marked as Overridable can be " 440 + "overridden."); 441 } 442 } 443 } 444 checkCompatChangeReadAndLogPermission()445 private void checkCompatChangeReadAndLogPermission() { 446 checkCompatChangeReadPermission(); 447 checkCompatChangeLogPermission(); 448 } 449 isShownInUI(CompatibilityChangeInfo change)450 private boolean isShownInUI(CompatibilityChangeInfo change) { 451 if (change.getLoggingOnly()) { 452 return false; 453 } 454 if (change.getId() == CompatChange.CTS_SYSTEM_API_CHANGEID) { 455 return false; 456 } 457 if (change.getEnableSinceTargetSdk() > 0) { 458 return change.getEnableSinceTargetSdk() >= Build.VERSION_CODES.Q 459 && change.getEnableSinceTargetSdk() <= mBuildClassifier.platformTargetSdk(); 460 } 461 return true; 462 } 463 464 /** 465 * Registers a listener for change state overrides. 466 * 467 * <p>Only one listener per change is allowed. 468 * 469 * <p>{@code listener.onCompatChange(String)} method is guaranteed to be called with 470 * packageName before the app is killed upon an override change. The state of a change is not 471 * guaranteed to change when {@code listener.onCompatChange(String)} is called. 472 * 473 * @param changeId to get updates for 474 * @param listener the listener that will be called upon a potential change for package 475 * @return {@code true} if a change with changeId was already known, or (@code false} 476 * otherwise 477 * @throws IllegalStateException if a listener was already registered for changeId 478 */ registerListener(long changeId, CompatChange.ChangeListener listener)479 public boolean registerListener(long changeId, CompatChange.ChangeListener listener) { 480 return mCompatConfig.registerListener(changeId, listener); 481 } 482 483 /** 484 * Registers a broadcast receiver that listens for package install, replace or remove. 485 * 486 * @param context the context where the receiver should be registered 487 */ registerPackageReceiver(Context context)488 public void registerPackageReceiver(Context context) { 489 final BroadcastReceiver receiver = new BroadcastReceiver() { 490 @Override 491 public void onReceive(Context context, Intent intent) { 492 if (intent == null) { 493 return; 494 } 495 final Uri packageData = intent.getData(); 496 if (packageData == null) { 497 return; 498 } 499 final String packageName = packageData.getSchemeSpecificPart(); 500 if (packageName == null) { 501 return; 502 } 503 mCompatConfig.recheckOverrides(packageName); 504 } 505 }; 506 IntentFilter filter = new IntentFilter(); 507 filter.addAction(Intent.ACTION_PACKAGE_ADDED); 508 filter.addAction(Intent.ACTION_PACKAGE_REPLACED); 509 filter.addAction(Intent.ACTION_PACKAGE_REMOVED); 510 filter.addDataScheme("package"); 511 context.registerReceiverForAllUsers(receiver, filter, /* broadcastPermission= */ 512 null, /* scheduler= */ null); 513 } 514 515 /** 516 * Registers the observer for 517 * {@link android.provider.Settings.Global#FORCE_NON_DEBUGGABLE_FINAL_BUILD_FOR_COMPAT}. 518 */ registerContentObserver()519 public void registerContentObserver() { 520 mCompatConfig.registerContentObserver(); 521 } 522 } 523