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