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