From 96694aa7bd2821e89fef2aba789cf2a88e212ef0 Mon Sep 17 00:00:00 2001 From: Winson Date: Wed, 16 Dec 2020 17:14:00 -0800 Subject: [PATCH] Attach domain verification states during package scan/install Handles adding a new package (either from a brand new install or a boot scan) and migrating between packages when installing an update. Will merge the package states in the update case to preserve successful verifications while removing failed domains so that they can be re-requested by the verification agent. Exempt-From-Owner-Approval: Already approved by owners on main branch Bug: 163565712 Test: manual, device boots, will be tested as part of later changes Change-Id: I54dc8415e10544e618905e269ab95ec0a2af0fb0 --- .../server/pm/PackageManagerService.java | 6 + .../android/server/pm/PackageSettingBase.java | 15 +- .../DomainVerificationManagerInternal.java | 39 +++ .../verify/DomainVerificationService.java | 230 +++++++++++++++++- .../verify/DomainVerificationSettings.java | 15 ++ .../java/com/android/server/SystemServer.java | 3 +- 6 files changed, 303 insertions(+), 5 deletions(-) diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index ba30247744a35..3d35f10b3c490 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -14576,6 +14576,12 @@ public class PackageManagerService extends IPackageManager.Stub mAppsFilter.addPackage(pkgSetting, isReplace); mPackageProperty.addAllProperties(pkg); + if (oldPkgSetting == null || oldPkgSetting.getPkg() == null) { + mDomainVerificationManager.addPackage(pkgSetting); + } else { + mDomainVerificationManager.migrateState(oldPkgSetting, pkgSetting); + } + int collectionSize = ArrayUtils.size(pkg.getInstrumentations()); StringBuilder r = null; int i; diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java index d123c7770cfe2..b69d2b015d6cd 100644 --- a/services/core/java/com/android/server/pm/PackageSettingBase.java +++ b/services/core/java/com/android/server/pm/PackageSettingBase.java @@ -42,6 +42,8 @@ import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.pm.domain.verify.DomainVerificationManagerInternal; +import com.android.server.pm.domain.verify.DomainVerificationService; import com.android.server.pm.parsing.pkg.AndroidPackage; import java.io.File; @@ -350,9 +352,18 @@ public abstract class PackageSettingBase extends SettingBase { return readUserState(userId).getSharedLibraryOverlayPaths(); } - /** Only use for testing. Do NOT use in production code. */ + /** + * Only use for testing. Do NOT use in production code. + * + * Unless you're {@link DomainVerificationService} and you need to migrate legacy state. + * This is done rather than passing in the user IDs to + * {@link DomainVerificationManagerInternal#addPackage(PackageSetting)} to make the v2 APIs + * completely correct, without legacy details, since that method inherently does not care about + * the users on the device. + */ @VisibleForTesting - SparseArray getUserState() { + @Deprecated + public SparseArray getUserState() { return mUserState; } diff --git a/services/core/java/com/android/server/pm/domain/verify/DomainVerificationManagerInternal.java b/services/core/java/com/android/server/pm/domain/verify/DomainVerificationManagerInternal.java index d83b49dee11eb..7e41eccc4f4ad 100644 --- a/services/core/java/com/android/server/pm/domain/verify/DomainVerificationManagerInternal.java +++ b/services/core/java/com/android/server/pm/domain/verify/DomainVerificationManagerInternal.java @@ -23,6 +23,7 @@ import android.content.pm.domain.verify.DomainVerificationSet; import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; +import com.android.server.pm.PackageSetting; import com.android.server.pm.domain.verify.models.DomainVerificationPkgState; import org.xmlpull.v1.XmlPullParserException; @@ -40,6 +41,44 @@ public interface DomainVerificationManagerInternal extends DomainVerificationMan @NonNull UUID generateNewId(); + /** + * Restores or creates internal state for the new package. This can either be from scanning a + * package at boot, or a truly new installation on the device. It is expected that the {@link + * PackageSetting#getDomainSetId()} already be set to the correct value. + *

+ * If this is from scan, there should be a pending state that was previous read using {@link + * #readSettings(TypedXmlPullParser)}, which will be attached as-is to the package. In this + * case, a broadcast will not be sent to the domain verification agent on device, as it is + * assumed nothing has changed since the device rebooted. + *

+ * If this is a new install, state will be restored from a previous call to {@link + * #restoreSettings(TypedXmlPullParser)}, or a new one will be generated. In either case, a + * broadcast will be sent to the domain verification agent so it may re-run any verification + * logic for the newly associated domains. + *

+ * This will mutate internal {@link DomainVerificationPkgState} and so will hold the internal + * lock. This should never be called from within the domain verification classes themselves. + *

+ * This will NOT call {@link #writeSettings(TypedXmlSerializer)}. That must be handled by the + * caller. + */ + void addPackage(@NonNull PackageSetting newPkgSetting); + + /** + * Migrates verification state from a previous install to a new one. It is expected that the + * {@link PackageSetting#getDomainSetId()} already be set to the correct value, usually from + * {@link #generateNewId()}. This will preserve {@link #STATE_SUCCESS} domains under the + * assumption that the new package will pass the same server side config as the previous + * package, as they have matching signatures. + *

+ * This will mutate internal {@link DomainVerificationPkgState} and so will hold the internal + * lock. This should never be called from within the domain verification classes themselves. + *

+ * This will NOT call {@link #writeSettings(TypedXmlSerializer)}. That must be handled by the + * caller. + */ + void migrateState(@NonNull PackageSetting oldPkgSetting, @NonNull PackageSetting newPkgSetting); + /** * Serializes the entire internal state. This is equivalent to a full backup of the existing * verification state. diff --git a/services/core/java/com/android/server/pm/domain/verify/DomainVerificationService.java b/services/core/java/com/android/server/pm/domain/verify/DomainVerificationService.java index 4d4a8484c440a..2494d715f821e 100644 --- a/services/core/java/com/android/server/pm/domain/verify/DomainVerificationService.java +++ b/services/core/java/com/android/server/pm/domain/verify/DomainVerificationService.java @@ -20,18 +20,32 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.Context; +import android.content.pm.IntentFilterVerificationInfo; +import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.PackageUserState; +import android.content.pm.domain.verify.DomainVerificationManager; import android.content.pm.domain.verify.DomainVerificationSet; +import android.content.pm.domain.verify.DomainVerificationState; import android.content.pm.domain.verify.DomainVerificationUserSelection; import android.content.pm.domain.verify.IDomainVerificationManager; +import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Singleton; +import android.util.Slog; +import android.util.SparseArray; import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; import com.android.internal.annotations.GuardedBy; +import com.android.server.SystemConfig; import com.android.server.SystemService; +import com.android.server.compat.PlatformCompat; +import com.android.server.pm.PackageSetting; import com.android.server.pm.domain.verify.models.DomainVerificationPkgState; import com.android.server.pm.domain.verify.models.DomainVerificationStateMap; +import com.android.server.pm.domain.verify.models.DomainVerificationUserState; +import com.android.server.pm.parsing.pkg.AndroidPackage; import org.xmlpull.v1.XmlPullParserException; @@ -63,17 +77,25 @@ public class DomainVerificationService extends SystemService @NonNull private final Singleton mConnection; + @NonNull + private final SystemConfig mSystemConfig; + @NonNull private final DomainVerificationSettings mSettings; + @NonNull + private final DomainVerificationCollector mCollector; + @NonNull private final IDomainVerificationManager.Stub mStub = new DomainVerificationManagerStub(this); - public DomainVerificationService(@NonNull Context context, - @NonNull Singleton connection) { + public DomainVerificationService(@NonNull Context context, @NonNull SystemConfig systemConfig, + @NonNull PlatformCompat platformCompat, @NonNull Singleton connection) { super(context); mConnection = connection; + mSystemConfig = systemConfig; mSettings = new DomainVerificationSettings(); + mCollector = new DomainVerificationCollector(platformCompat, systemConfig); } @Override @@ -149,6 +171,206 @@ public class DomainVerificationService extends SystemService return UUID.randomUUID(); } + @Override + public void migrateState(@NonNull PackageSetting oldPkgSetting, + @NonNull PackageSetting newPkgSetting) { + String pkgName = newPkgSetting.name; + boolean sendBroadcast; + + synchronized (mLock) { + UUID oldDomainSetId = oldPkgSetting.getDomainSetId(); + UUID newDomainSetId = newPkgSetting.getDomainSetId(); + DomainVerificationPkgState oldPkgState = mAttachedPkgStates.remove(oldDomainSetId); + + AndroidPackage oldPkg = oldPkgSetting.getPkg(); + AndroidPackage newPkg = newPkgSetting.getPkg(); + + ArrayMap newStateMap = new ArrayMap<>(); + SparseArray newUserStates = new SparseArray<>(); + + if (oldPkgState == null || oldPkg == null || newPkg == null) { + // Should be impossible, but to be safe, continue with a new blank state instead + Slog.wtf(TAG, "Invalid state nullability old state = " + oldPkgState + + ", old pkgSetting = " + oldPkgSetting + + ", new pkgSetting = " + newPkgSetting + + ", old pkg = " + oldPkg + + ", new pkg = " + newPkg, new Exception()); + + DomainVerificationPkgState newPkgState = new DomainVerificationPkgState( + pkgName, newDomainSetId, true, newStateMap, newUserStates); + mAttachedPkgStates.put(pkgName, newDomainSetId, newPkgState); + return; + } + + ArrayMap oldStateMap = oldPkgState.getStateMap(); + ArraySet newAutoVerifyDomains = mCollector.collectAutoVerifyDomains(newPkg); + int newDomainsSize = newAutoVerifyDomains.size(); + + for (int newDomainsIndex = 0; newDomainsIndex < newDomainsSize; newDomainsIndex++) { + String domain = newAutoVerifyDomains.valueAt(newDomainsIndex); + Integer oldStateInteger = oldStateMap.get(domain); + if (oldStateInteger != null) { + int oldState = oldStateInteger; + switch (oldState) { + case DomainVerificationState.STATE_SUCCESS: + case DomainVerificationState.STATE_RESTORED: + case DomainVerificationState.STATE_MIGRATED: + newStateMap.put(domain, oldState); + break; + default: + // In all other cases, the state code is left unset + // (STATE_NO_RESPONSE) to signal to the verification agent that any + // existing error has been cleared and the domain should be + // re-attempted. This makes update of a package a signal to + // re-verify. + break; + } + } + } + + SparseArray oldUserStates = + oldPkgState.getUserSelectionStates(); + int oldUserStatesSize = oldUserStates.size(); + if (oldUserStatesSize > 0) { + ArraySet newWebDomains = mCollector.collectAutoVerifyDomains(newPkg); + for (int oldUserStatesIndex = 0; oldUserStatesIndex < oldUserStatesSize; + oldUserStatesIndex++) { + int userId = oldUserStates.keyAt(oldUserStatesIndex); + DomainVerificationUserState oldUserState = oldUserStates.valueAt( + oldUserStatesIndex); + ArraySet oldEnabledHosts = oldUserState.getEnabledHosts(); + ArraySet newEnabledHosts = new ArraySet<>(oldEnabledHosts); + newEnabledHosts.retainAll(newWebDomains); + DomainVerificationUserState newUserState = new DomainVerificationUserState( + userId, newEnabledHosts, oldUserState.isDisallowLinkHandling()); + newUserStates.put(userId, newUserState); + } + } + + boolean hasAutoVerifyDomains = newDomainsSize > 0; + boolean stateApplied = applyImmutableState(pkgName, newStateMap, newAutoVerifyDomains); + + // TODO(b/159952358): sendBroadcast should be abstracted so it doesn't have to be aware + // of whether/what state was applied. Probably some method which iterates the map to + // check for any domains that actually have state changeable by the domain verification + // agent. + sendBroadcast = hasAutoVerifyDomains && !stateApplied; + + mAttachedPkgStates.put(pkgName, newDomainSetId, new DomainVerificationPkgState( + pkgName, newDomainSetId, hasAutoVerifyDomains, newStateMap, newUserStates)); + } + + if (sendBroadcast) { + sendBroadcastForPackage(pkgName); + } + } + + // TODO(b/159952358): Handle valid domainSetIds for PackageSettings with no AndroidPackage + @Override + public void addPackage(@NonNull PackageSetting newPkgSetting) { + // TODO(b/159952358): Optimize packages without any domains. Those wouldn't have to be in + // the state map, but it would require handling the "migration" case where an app either + // gains or loses all domains. + + UUID domainSetId = newPkgSetting.getDomainSetId(); + String pkgName = newPkgSetting.name; + + boolean sendBroadcast = true; + + DomainVerificationPkgState pkgState; + pkgState = mSettings.getPendingState(pkgName); + if (pkgState != null) { + // Don't send when attaching from pending read, which is usually boot scan. Re-send on + // boot is handled in a separate method once all packages are added. + sendBroadcast = false; + } else { + pkgState = mSettings.getRestoredState(pkgName); + } + + AndroidPackage pkg = newPkgSetting.getPkg(); + ArraySet domains = mCollector.collectAutoVerifyDomains(pkg); + boolean hasAutoVerifyDomains = !domains.isEmpty(); + boolean isPendingOrRestored = pkgState != null; + if (isPendingOrRestored) { + pkgState.setId(domainSetId); + } else { + pkgState = new DomainVerificationPkgState(pkgName, domainSetId, hasAutoVerifyDomains); + } + + boolean stateApplied = applyImmutableState(pkgState, domains); + if (!stateApplied && !isPendingOrRestored) { + // TODO(b/159952358): Test this behavior + // Attempt to preserve user experience by automatically verifying all domains from + // legacy state if they were previously approved, or by automatically enabling all + // hosts through user selection if legacy state indicates a user previously made the + // choice in settings to allow supported links. The domain verification agent should + // re-verify these links (set to STATE_MIGRATED) at the next possible opportunity, + // and disable them if appropriate. + ArraySet webDomains = null; + + @SuppressWarnings("deprecation") + SparseArray userState = newPkgSetting.getUserState(); + int userStateSize = userState.size(); + for (int index = 0; index < userStateSize; index++) { + int userId = userState.keyAt(index); + int legacyStatus = userState.valueAt(index).domainVerificationStatus; + if (legacyStatus + == PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) { + if (webDomains == null) { + webDomains = mCollector.collectAllWebDomains(pkg); + } + + pkgState.getOrCreateUserSelectionState(userId).addHosts(webDomains); + } + } + + IntentFilterVerificationInfo legacyInfo = + newPkgSetting.getIntentFilterVerificationInfo(); + if (legacyInfo != null + && legacyInfo.getStatus() + == PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) { + ArrayMap stateMap = pkgState.getStateMap(); + int domainsSize = domains.size(); + for (int index = 0; index < domainsSize; index++) { + stateMap.put(domains.valueAt(index), DomainVerificationState.STATE_MIGRATED); + } + } + } + + synchronized (mLock) { + mAttachedPkgStates.put(pkgName, domainSetId, pkgState); + } + + if (sendBroadcast && hasAutoVerifyDomains) { + sendBroadcastForPackage(pkgName); + } + } + + private boolean applyImmutableState(@NonNull DomainVerificationPkgState pkgState, + @NonNull ArraySet autoVerifyDomains) { + return applyImmutableState(pkgState.getPackageName(), pkgState.getStateMap(), + autoVerifyDomains); + } + + /** + * Applies any immutable state as the final step when adding or migrating state. Currently only + * applies {@link SystemConfig#getLinkedApps()}, which approves all domains for a package. + */ + private boolean applyImmutableState(@NonNull String packageName, + @NonNull ArrayMap stateMap, + @NonNull ArraySet autoVerifyDomains) { + if (mSystemConfig.getLinkedApps().contains(packageName)) { + int domainsSize = autoVerifyDomains.size(); + for (int index = 0; index < domainsSize; index++) { + stateMap.put(autoVerifyDomains.valueAt(index), + DomainVerificationState.STATE_APPROVED); + } + return true; + } + + return false; + } + @Override public void writeSettings(@NonNull TypedXmlSerializer serializer) throws IOException { synchronized (mLock) { @@ -195,6 +417,10 @@ public class DomainVerificationService extends SystemService mConnection.get().scheduleWriteSettings(); } + private void sendBroadcastForPackage(@NonNull String packageName) { + // TODO(b/159952358): Implement proxy + } + public interface Connection { /** diff --git a/services/core/java/com/android/server/pm/domain/verify/DomainVerificationSettings.java b/services/core/java/com/android/server/pm/domain/verify/DomainVerificationSettings.java index 8de2ae614fe68..185fd62411570 100644 --- a/services/core/java/com/android/server/pm/domain/verify/DomainVerificationSettings.java +++ b/services/core/java/com/android/server/pm/domain/verify/DomainVerificationSettings.java @@ -17,6 +17,7 @@ package com.android.server.pm.domain.verify; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.pm.domain.verify.DomainVerificationState; import android.os.UserHandle; @@ -253,4 +254,18 @@ class DomainVerificationSettings { mRestoredPkgStates.valueAt(index).removeUser(userId); } } + + @Nullable + public DomainVerificationPkgState getPendingState(@NonNull String pkgName) { + synchronized (mLock) { + return mPendingPkgStates.get(pkgName); + } + } + + @Nullable + public DomainVerificationPkgState getRestoredState(@NonNull String pkgName) { + synchronized (mLock) { + return mRestoredPkgStates.get(pkgName); + } + } } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index b39a9a8d2297c..b09d8a67577fb 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1064,7 +1064,8 @@ public final class SystemServer implements Dumpable { t.traceBegin("StartDomainVerificationService"); DomainVerificationService domainVerificationService = new DomainVerificationService( - mSystemContext, new Singleton() { + mSystemContext, SystemConfig.getInstance(), platformCompat, + new Singleton() { @Override protected DomainVerificationService.Connection create() { // Deferred retrieval from PackageManagerService, since PMS is initialized after