diff options
12 files changed, 421 insertions, 119 deletions
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 7fe2a41cc4b9..7221ae97c57f 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -310,8 +310,8 @@ interface IPackageManager { void restorePreferredActivities(in byte[] backup, int userId); byte[] getDefaultAppsBackup(int userId); void restoreDefaultApps(in byte[] backup, int userId); - byte[] getIntentFilterVerificationBackup(int userId); - void restoreIntentFilterVerification(in byte[] backup, int userId); + byte[] getDomainVerificationBackup(int userId); + void restoreDomainVerification(in byte[] backup, int userId); /** * Report the set of 'Home' activity candidates, plus (if any) which of them diff --git a/core/java/com/android/server/backup/PreferredActivityBackupHelper.java b/core/java/com/android/server/backup/PreferredActivityBackupHelper.java index 80636706a9a6..503c71990adb 100644 --- a/core/java/com/android/server/backup/PreferredActivityBackupHelper.java +++ b/core/java/com/android/server/backup/PreferredActivityBackupHelper.java @@ -16,10 +16,12 @@ package com.android.server.backup; +import android.annotation.StringDef; +import android.annotation.UserIdInt; import android.app.AppGlobals; import android.app.backup.BlobBackupHelper; import android.content.pm.IPackageManager; -import android.os.UserHandle; +import android.content.pm.verify.domain.DomainVerificationManager; import android.util.Slog; public class PreferredActivityBackupHelper extends BlobBackupHelper { @@ -27,7 +29,7 @@ public class PreferredActivityBackupHelper extends BlobBackupHelper { private static final boolean DEBUG = false; // current schema of the backup state blob - private static final int STATE_VERSION = 3; + private static final int STATE_VERSION = 4; // key under which the preferred-activity state blob is committed to backup private static final String KEY_PREFERRED = "preferred-activity"; @@ -35,14 +37,41 @@ public class PreferredActivityBackupHelper extends BlobBackupHelper { // key for default-browser [etc] state private static final String KEY_DEFAULT_APPS = "default-apps"; - // intent-filter verification state + /** + * Intent-filter verification state + * @deprecated Replaced by {@link #KEY_DOMAIN_VERIFICATION}, retained to ensure the key is + * never reused. + */ + @Deprecated private static final String KEY_INTENT_VERIFICATION = "intent-verification"; - public PreferredActivityBackupHelper() { - super(STATE_VERSION, - KEY_PREFERRED, - KEY_DEFAULT_APPS, - KEY_INTENT_VERIFICATION); + /** + * State for {@link DomainVerificationManager}. + */ + private static final String KEY_DOMAIN_VERIFICATION = "domain-verification"; + + private static final String[] KEYS = new String[] { + KEY_PREFERRED, + KEY_DEFAULT_APPS, + KEY_INTENT_VERIFICATION, + KEY_DOMAIN_VERIFICATION + }; + + @StringDef(value = { + KEY_PREFERRED, + KEY_DEFAULT_APPS, + KEY_INTENT_VERIFICATION, + KEY_DOMAIN_VERIFICATION + }) + private @interface Key { + } + + @UserIdInt + private final int mUserId; + + public PreferredActivityBackupHelper(@UserIdInt int userId) { + super(STATE_VERSION, KEYS); + mUserId = userId; } @Override @@ -52,14 +81,16 @@ public class PreferredActivityBackupHelper extends BlobBackupHelper { Slog.d(TAG, "Handling backup of " + key); } try { - // TODO: http://b/22388012 switch (key) { case KEY_PREFERRED: - return pm.getPreferredActivityBackup(UserHandle.USER_SYSTEM); + return pm.getPreferredActivityBackup(mUserId); case KEY_DEFAULT_APPS: - return pm.getDefaultAppsBackup(UserHandle.USER_SYSTEM); + return pm.getDefaultAppsBackup(mUserId); case KEY_INTENT_VERIFICATION: - return pm.getIntentFilterVerificationBackup(UserHandle.USER_SYSTEM); + // Deprecated + return null; + case KEY_DOMAIN_VERIFICATION: + return pm.getDomainVerificationBackup(mUserId); default: Slog.w(TAG, "Unexpected backup key " + key); } @@ -70,22 +101,24 @@ public class PreferredActivityBackupHelper extends BlobBackupHelper { } @Override - protected void applyRestoredPayload(String key, byte[] payload) { + protected void applyRestoredPayload(@Key String key, byte[] payload) { IPackageManager pm = AppGlobals.getPackageManager(); if (DEBUG) { Slog.d(TAG, "Handling restore of " + key); } try { - // TODO: http://b/22388012 switch (key) { case KEY_PREFERRED: - pm.restorePreferredActivities(payload, UserHandle.USER_SYSTEM); + pm.restorePreferredActivities(payload, mUserId); break; case KEY_DEFAULT_APPS: - pm.restoreDefaultApps(payload, UserHandle.USER_SYSTEM); + pm.restoreDefaultApps(payload, mUserId); break; case KEY_INTENT_VERIFICATION: - pm.restoreIntentFilterVerification(payload, UserHandle.USER_SYSTEM); + // Deprecated + break; + case KEY_DOMAIN_VERIFICATION: + pm.restoreDomainVerification(payload, mUserId); break; default: Slog.w(TAG, "Unexpected restore key " + key); diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java index d98298cbef5a..fa1820456fb9 100644 --- a/services/core/java/com/android/server/backup/SystemBackupAgent.java +++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java @@ -94,7 +94,7 @@ public class SystemBackupAgent extends BackupAgentHelper { mUserId = user.getIdentifier(); addHelper(SYNC_SETTINGS_HELPER, new AccountSyncSettingsBackupHelper(this, mUserId)); - addHelper(PREFERRED_HELPER, new PreferredActivityBackupHelper()); + addHelper(PREFERRED_HELPER, new PreferredActivityBackupHelper(mUserId)); addHelper(NOTIFICATION_HELPER, new NotificationBackupHelper(mUserId)); addHelper(PERMISSION_HELPER, new PermissionBackupHelper(mUserId)); addHelper(USAGE_STATS_HELPER, new UsageStatsBackupHelper(this)); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 321c5cabc1c1..b270e841923b 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1789,6 +1789,24 @@ public class PackageManagerService extends IPackageManager.Stub } @Override + public <ExceptionOne extends Exception, ExceptionTwo extends Exception> void + withPackageSettingsThrowing2( + @NonNull Throwing2Consumer<Function<String, PackageSetting>, ExceptionOne, + ExceptionTwo> block) throws ExceptionOne, ExceptionTwo { + final Computer snapshot = snapshotComputer(); + + // This method needs to either lock or not lock consistently throughout the method, + // so if the live computer is returned, force a wrapping sync block. + if (snapshot == mLiveComputer) { + synchronized (mLock) { + block.accept(snapshot::getPackageSetting); + } + } else { + block.accept(snapshot::getPackageSetting); + } + } + + @Override public <Output, ExceptionType extends Exception> Output withPackageSettingsReturningThrowing(@NonNull ThrowingFunction<Function<String, PackageSetting>, Output, ExceptionType> block) throws ExceptionType { @@ -22618,21 +22636,44 @@ public class PackageManagerService extends IPackageManager.Stub } @Override - public byte[] getIntentFilterVerificationBackup(int userId) { + public byte[] getDomainVerificationBackup(int userId) { if (Binder.getCallingUid() != Process.SYSTEM_UID) { - throw new SecurityException("Only the system may call getIntentFilterVerificationBackup()"); + throw new SecurityException("Only the system may call getDomainVerificationBackup()"); } - // TODO(b/170746586) - return null; + try { + try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { + TypedXmlSerializer serializer = Xml.resolveSerializer(output); + mDomainVerificationManager.writeSettings(serializer, true, userId); + return output.toByteArray(); + } + } catch (Exception e) { + if (DEBUG_BACKUP) { + Slog.e(TAG, "Unable to write domain verification for backup", e); + } + return null; + } } @Override - public void restoreIntentFilterVerification(byte[] backup, int userId) { + public void restoreDomainVerification(byte[] backup, int userId) { if (Binder.getCallingUid() != Process.SYSTEM_UID) { throw new SecurityException("Only the system may call restorePreferredActivities()"); } - // TODO(b/170746586) + + try { + ByteArrayInputStream input = new ByteArrayInputStream(backup); + TypedXmlPullParser parser = Xml.resolvePullParser(input); + + // User ID input isn't necessary here as it assumes the user integers match and that + // the only states inside the backup XML are for the target user. + mDomainVerificationManager.restoreSettings(parser); + input.close(); + } catch (Exception e) { + if (DEBUG_BACKUP) { + Slog.e(TAG, "Exception restoring domain verification: " + e.getMessage()); + } + } } @Override diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 4823c29b96f9..8ebb8a203841 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -2323,7 +2323,8 @@ public final class Settings implements Watchable, Snappable { } } - mDomainVerificationManager.writeSettings(serializer); + mDomainVerificationManager.writeSettings(serializer, false /* includeSignatures */, + UserHandle.USER_ALL); mKeySetManagerService.writeKeySetManagerServiceLPr(serializer); @@ -2900,13 +2901,7 @@ public final class Settings implements Watchable, Snappable { } str.close(); - - } catch (XmlPullParserException e) { - mReadMessages.append("Error reading: " + e.toString()); - PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading settings: " + e); - Slog.wtf(PackageManagerService.TAG, "Error reading package manager settings", e); - - } catch (java.io.IOException e) { + } catch (IOException | XmlPullParserException e) { mReadMessages.append("Error reading: " + e.toString()); PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading settings: " + e); Slog.wtf(PackageManagerService.TAG, "Error reading package manager settings", e); diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java index 0f99e1963f28..34e3efb06ecc 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java @@ -181,8 +181,8 @@ public interface DomainVerificationManagerInternal { * 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. * <p> - * This will NOT call {@link #writeSettings(TypedXmlSerializer)}. That must be handled by the - * caller. + * This will NOT call {@link #writeSettings(TypedXmlSerializer, boolean, int)}. That must be + * handled by the caller. */ void addPackage(@NonNull PackageSetting newPkgSetting); @@ -196,20 +196,27 @@ public interface DomainVerificationManagerInternal { * 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. * <p> - * This will NOT call {@link #writeSettings(TypedXmlSerializer)}. That must be handled by the - * caller. + * This will NOT call {@link #writeSettings(TypedXmlSerializer, boolean, int)}. 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. This write includes legacy state, as a sibling tag the modern state. + * + * @param includeSignatures Whether to include the package signatures in the output, mainly + * used for backing up the user settings and ensuring they're + * re-attached to the same package. + * @param userId The user to write out. Supports {@link UserHandle#USER_ALL} if all users + * should be written. */ - void writeSettings(@NonNull TypedXmlSerializer serializer) throws IOException; + void writeSettings(@NonNull TypedXmlSerializer serializer, boolean includeSignatures, + @UserIdInt int userId) throws IOException; /** * Read back a list of {@link DomainVerificationPkgState}s previously written by {@link - * #writeSettings(TypedXmlSerializer)}. Assumes that the + * #writeSettings(TypedXmlSerializer, boolean, int)}. Assumes that the * {@link DomainVerificationPersistence#TAG_DOMAIN_VERIFICATIONS} tag has already been entered. * <p> * This is expected to only be used to re-attach states for packages already known to be on the @@ -245,7 +252,7 @@ public interface DomainVerificationManagerInternal { /** * Restore a list of {@link DomainVerificationPkgState}s previously written by {@link - * #writeSettings(TypedXmlSerializer)}. Assumes that the + * #writeSettings(TypedXmlSerializer, boolean, int)}. Assumes that the * {@link DomainVerificationPersistence#TAG_DOMAIN_VERIFICATIONS} * tag has already been entered. * <p> @@ -350,7 +357,7 @@ public interface DomainVerificationManagerInternal { /** * Notify that a settings change has been made and that eventually - * {@link #writeSettings(TypedXmlSerializer)} should be invoked by the parent. + * {@link #writeSettings(TypedXmlSerializer, boolean, int)} should be invoked by the parent. */ void scheduleWriteSettings(); @@ -394,6 +401,15 @@ public interface DomainVerificationManagerInternal { throws ExceptionType; /** + * Variant which throws 2 exceptions. + * @see #withPackageSettings(Consumer) + */ + <ExceptionOne extends Exception, ExceptionTwo extends Exception> void + withPackageSettingsThrowing2( + @NonNull Throwing2Consumer<Function<String, PackageSetting>, ExceptionOne, + ExceptionTwo> block) throws ExceptionOne, ExceptionTwo; + + /** * Variant which returns a value to the caller and throws. * @see #withPackageSettings(Consumer) */ @@ -408,6 +424,11 @@ public interface DomainVerificationManagerInternal { void accept(Input input) throws ExceptionType; } + interface Throwing2Consumer<Input, ExceptionOne extends Exception, + ExceptionTwo extends Exception> { + void accept(Input input) throws ExceptionOne, ExceptionTwo; + } + interface ThrowingFunction<Input, Output, ExceptionType extends Exception> { Output apply(Input input) throws ExceptionType; } diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java index f0ad98c26ede..e803457fcb97 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java @@ -18,8 +18,10 @@ package com.android.server.pm.verify.domain; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.content.pm.Signature; import android.content.pm.verify.domain.DomainVerificationState; +import android.os.UserHandle; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; @@ -77,7 +79,8 @@ public class DomainVerificationPersistence { @NonNull DomainVerificationStateMap<DomainVerificationPkgState> attached, @NonNull ArrayMap<String, DomainVerificationPkgState> pending, @NonNull ArrayMap<String, DomainVerificationPkgState> restored, - @Nullable Function<String, String> pkgNameToSignature) throws IOException { + @UserIdInt int userId, @Nullable Function<String, String> pkgNameToSignature) + throws IOException { try (SettingsXml.Serializer serializer = SettingsXml.serializer(xmlSerializer)) { try (SettingsXml.WriteSection ignored = serializer.startSection( TAG_DOMAIN_VERIFICATIONS)) { @@ -98,26 +101,27 @@ public class DomainVerificationPersistence { } try (SettingsXml.WriteSection activeSection = serializer.startSection(TAG_ACTIVE)) { - writePackageStates(activeSection, active, pkgNameToSignature); + writePackageStates(activeSection, active, userId, pkgNameToSignature); } try (SettingsXml.WriteSection restoredSection = serializer.startSection( TAG_RESTORED)) { - writePackageStates(restoredSection, restored.values(), pkgNameToSignature); + writePackageStates(restoredSection, restored.values(), userId, + pkgNameToSignature); } } } } private static void writePackageStates(@NonNull SettingsXml.WriteSection section, - @NonNull Collection<DomainVerificationPkgState> states, + @NonNull Collection<DomainVerificationPkgState> states, int userId, @Nullable Function<String, String> pkgNameToSignature) throws IOException { if (states.isEmpty()) { return; } for (DomainVerificationPkgState state : states) { - writePkgStateToXml(section, state, pkgNameToSignature); + writePkgStateToXml(section, state, userId, pkgNameToSignature); } } @@ -211,7 +215,7 @@ public class DomainVerificationPersistence { } private static void writePkgStateToXml(@NonNull SettingsXml.WriteSection parentSection, - @NonNull DomainVerificationPkgState pkgState, + @NonNull DomainVerificationPkgState pkgState, @UserIdInt int userId, @Nullable Function<String, String> pkgNameToSignature) throws IOException { String packageName = pkgState.getPackageName(); String signature = pkgNameToSignature == null @@ -231,11 +235,12 @@ public class DomainVerificationPersistence { pkgState.isHasAutoVerifyDomains()) .attribute(ATTR_SIGNATURE, signature)) { writeStateMap(parentSection, pkgState.getStateMap()); - writeUserStates(parentSection, pkgState.getUserStates()); + writeUserStates(parentSection, userId, pkgState.getUserStates()); } } private static void writeUserStates(@NonNull SettingsXml.WriteSection parentSection, + @UserIdInt int userId, @NonNull SparseArray<DomainVerificationInternalUserState> states) throws IOException { int size = states.size(); if (size == 0) { @@ -243,8 +248,15 @@ public class DomainVerificationPersistence { } try (SettingsXml.WriteSection section = parentSection.startSection(TAG_USER_STATES)) { - for (int index = 0; index < size; index++) { - writeUserStateToXml(section, states.valueAt(index)); + if (userId == UserHandle.USER_ALL) { + for (int index = 0; index < size; index++) { + writeUserStateToXml(section, states.valueAt(index)); + } + } else { + DomainVerificationInternalUserState userState = states.get(userId); + if (userState != null) { + writeUserStateToXml(section, userState); + } } } } @@ -278,7 +290,7 @@ public class DomainVerificationPersistence { return null; } - boolean allowLinkHandling = section.getBoolean(ATTR_ALLOW_LINK_HANDLING, true); + boolean allowLinkHandling = section.getBoolean(ATTR_ALLOW_LINK_HANDLING, false); ArraySet<String> enabledHosts = new ArraySet<>(); SettingsXml.ChildSection child = section.children(); diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java index 4ae79a209524..1d20a1510c44 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java @@ -129,10 +129,10 @@ public class DomainVerificationService extends SystemService private final PlatformCompat mPlatformCompat; @NonNull - private final DomainVerificationSettings mSettings; + private final DomainVerificationCollector mCollector; @NonNull - private final DomainVerificationCollector mCollector; + private final DomainVerificationSettings mSettings; @NonNull private final DomainVerificationEnforcer mEnforcer; @@ -159,8 +159,8 @@ public class DomainVerificationService extends SystemService super(context); mSystemConfig = systemConfig; mPlatformCompat = platformCompat; - mSettings = new DomainVerificationSettings(); mCollector = new DomainVerificationCollector(platformCompat, systemConfig); + mSettings = new DomainVerificationSettings(mCollector); mEnforcer = new DomainVerificationEnforcer(context); mDebug = new DomainVerificationDebug(mCollector); mShell = new DomainVerificationShell(this); @@ -1026,21 +1026,29 @@ public class DomainVerificationService extends SystemService } @Override - public void writeSettings(@NonNull TypedXmlSerializer serializer) throws IOException { + public void writeSettings(@NonNull TypedXmlSerializer serializer, boolean includeSignatures, + @UserIdInt int userId) + throws IOException { mConnection.withPackageSettingsThrowing(pkgSettings -> { synchronized (mLock) { - mSettings.writeSettings(serializer, mAttachedPkgStates, pkgName -> { - PackageSetting pkgSetting = pkgSettings.apply(pkgName); - if (pkgSetting == null) { - // If querying for a user restored package that isn't installed on the - // device yet, there will be no signature to write out. In that case, - // it's expected that this returns null and it falls back to the restored - // state's stored signature if it exists. - return null; - } + Function<String, String> pkgNameToSignature = null; + if (includeSignatures) { + pkgNameToSignature = pkgName -> { + PackageSetting pkgSetting = pkgSettings.apply(pkgName); + if (pkgSetting == null) { + // If querying for a user restored package that isn't installed on the + // device yet, there will be no signature to write out. In that case, + // it's expected that this returns null and it falls back to the + // restored state's stored signature if it exists. + return null; + } - return PackageUtils.computeSignaturesSha256Digest(pkgSetting.getSignatures()); - }); + return PackageUtils.computeSignaturesSha256Digest( + pkgSetting.getSignatures()); + }; + } + + mSettings.writeSettings(serializer, mAttachedPkgStates, userId, pkgNameToSignature); } }); @@ -1048,11 +1056,14 @@ public class DomainVerificationService extends SystemService } @Override - public void readSettings(@NonNull TypedXmlPullParser parser) - throws IOException, XmlPullParserException { - synchronized (mLock) { - mSettings.readSettings(parser, mAttachedPkgStates); - } + public void readSettings(@NonNull TypedXmlPullParser parser) throws IOException, + XmlPullParserException { + mConnection.<IOException, XmlPullParserException>withPackageSettingsThrowing2( + pkgSettings -> { + synchronized (mLock) { + mSettings.readSettings(parser, mAttachedPkgStates, pkgSettings); + } + }); } @Override @@ -1064,9 +1075,12 @@ public class DomainVerificationService extends SystemService @Override public void restoreSettings(@NonNull TypedXmlPullParser parser) throws IOException, XmlPullParserException { - synchronized (mLock) { - mSettings.restoreSettings(parser, mAttachedPkgStates); - } + mConnection.<IOException, XmlPullParserException>withPackageSettingsThrowing2( + pkgSettings -> { + synchronized (mLock) { + mSettings.restoreSettings(parser, mAttachedPkgStates, pkgSettings); + } + }); } @Override @@ -1893,6 +1907,15 @@ public class DomainVerificationService extends SystemService } @Override + public <ExceptionOne extends Exception, ExceptionTwo extends Exception> void + withPackageSettingsThrowing2( + @NonNull Throwing2Consumer<Function<String, PackageSetting>, ExceptionOne, + ExceptionTwo> block) throws ExceptionOne, ExceptionTwo { + enforceLocking(); + mConnection.withPackageSettingsThrowing2(block); + } + + @Override public <Output, ExceptionType extends Exception> Output withPackageSettingsReturningThrowing(@NonNull ThrowingFunction<Function<String, PackageSetting>, Output, ExceptionType> block) throws ExceptionType { diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java index c8e95b585b7a..3b2990e33963 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java @@ -20,7 +20,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.pm.verify.domain.DomainVerificationState; -import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; import android.util.SparseArray; @@ -29,6 +28,8 @@ import android.util.TypedXmlSerializer; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.pm.PackageSetting; +import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState; import com.android.server.pm.verify.domain.models.DomainVerificationPkgState; import com.android.server.pm.verify.domain.models.DomainVerificationStateMap; @@ -36,10 +37,15 @@ import com.android.server.pm.verify.domain.models.DomainVerificationStateMap; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.util.Collections; +import java.util.Set; import java.util.function.Function; class DomainVerificationSettings { + @NonNull + private final DomainVerificationCollector mCollector; + /** * States read from disk that have yet to attach to a package, but are expected to, generally in * the context of scanning packages already on disk. This is expected to be empty once the boot @@ -67,24 +73,35 @@ class DomainVerificationSettings { */ private final Object mLock = new Object(); + public DomainVerificationSettings(@NonNull DomainVerificationCollector collector) { + mCollector = collector; + } + + public void writeSettings(@NonNull TypedXmlSerializer xmlSerializer, + @NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState, + @NonNull Function<String, String> pkgSignatureFunction) { + + } public void writeSettings(@NonNull TypedXmlSerializer xmlSerializer, @NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState, - @NonNull Function<String, String> pkgSignatureFunction) throws IOException { + @UserIdInt int userId, @NonNull Function<String, String> pkgSignatureFunction) + throws IOException { synchronized (mLock) { DomainVerificationPersistence.writeToXml(xmlSerializer, liveState, - mPendingPkgStates, mRestoredPkgStates, pkgSignatureFunction); + mPendingPkgStates, mRestoredPkgStates, userId, pkgSignatureFunction); } } /** * Parses a previously stored set of states and merges them with {@param liveState}, directly * mutating the values. This is intended for reading settings written by {@link - * #writeSettings(TypedXmlSerializer, DomainVerificationStateMap, Function)} on the same device - * setup. + * #writeSettings(TypedXmlSerializer, DomainVerificationStateMap, int, Function)} on the same + * device setup. */ public void readSettings(@NonNull TypedXmlPullParser parser, - @NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState) + @NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState, + @NonNull Function<String, PackageSetting> pkgSettingFunction) throws IOException, XmlPullParserException { DomainVerificationPersistence.ReadResult result = DomainVerificationPersistence.readFromXml(parser); @@ -101,7 +118,7 @@ class DomainVerificationSettings { // This branch should never be possible. Settings should be read from disk // before any states are attached. But just in case, handle it. if (!existingState.getId().equals(pkgState.getId())) { - mergePkgState(existingState, pkgState); + mergePkgState(existingState, pkgState, pkgSettingFunction); } } else { mPendingPkgStates.put(pkgName, pkgState); @@ -121,7 +138,8 @@ class DomainVerificationSettings { * mutating the values. This is intended for restoration across device setups. */ public void restoreSettings(@NonNull TypedXmlPullParser parser, - @NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState) + @NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState, + @NonNull Function<String, PackageSetting> pkgSettingFunction) throws IOException, XmlPullParserException { // TODO(b/170746586): Restoration assumes user IDs match, which is probably not the case on // a new device. @@ -148,7 +166,7 @@ class DomainVerificationSettings { } if (existingState != null) { - mergePkgState(existingState, newState); + mergePkgState(existingState, newState, pkgSettingFunction); } else { // If there's no existing state, that means the new state has to be transformed // in preparation for attaching to brand new package that may eventually be @@ -190,31 +208,34 @@ class DomainVerificationSettings { * specific error codes are fresher than the restored state. Essentially state is only restored * to grant additional verifications to an app. * <p> - * For user selection state, presence in either state will be considered an enabled host. NOTE: - * only {@link UserHandle#USER_SYSTEM} is merged. There is no restore path in place for - * multiple users. - * <p> - * TODO(b/170746586): Figure out the restore path for multiple users - * <p> - * This will mutate {@param oldState} to contain the merged state. + * For user selection state, presence in either state will be considered an enabled host. This + * assumes that all user IDs on the device match. If this isn't the case, then restore may set + * unexpected values. + * + * NOTE: This will mutate {@param oldState} to contain the merged state. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) - public static void mergePkgState(@NonNull DomainVerificationPkgState oldState, - @NonNull DomainVerificationPkgState newState) { + public void mergePkgState(@NonNull DomainVerificationPkgState oldState, + @NonNull DomainVerificationPkgState newState, + @NonNull Function<String, PackageSetting> pkgSettingFunction) { + PackageSetting pkgSetting = pkgSettingFunction.apply(oldState.getPackageName()); + AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg(); + Set<String> validDomains = pkg == null + ? Collections.emptySet() : mCollector.collectValidAutoVerifyDomains(pkg); + ArrayMap<String, Integer> oldStateMap = oldState.getStateMap(); ArrayMap<String, Integer> newStateMap = newState.getStateMap(); int size = newStateMap.size(); for (int index = 0; index < size; index++) { String domain = newStateMap.keyAt(index); Integer newStateCode = newStateMap.valueAt(index); - Integer oldStateCodeInteger = oldStateMap.get(domain); - if (oldStateCodeInteger == null) { + if (!validDomains.contains(domain)) { // Cannot add domains to an app continue; } - int oldStateCode = oldStateCodeInteger; - if (oldStateCode == DomainVerificationState.STATE_NO_RESPONSE) { + Integer oldStateCode = oldStateMap.get(domain); + if (oldStateCode == null || oldStateCode == DomainVerificationState.STATE_NO_RESPONSE) { if (newStateCode == DomainVerificationState.STATE_SUCCESS || newStateCode == DomainVerificationState.STATE_RESTORED) { oldStateMap.put(domain, DomainVerificationState.STATE_RESTORED); @@ -228,21 +249,21 @@ class DomainVerificationSettings { SparseArray<DomainVerificationInternalUserState> newSelectionStates = newState.getUserStates(); - DomainVerificationInternalUserState newUserState = - newSelectionStates.get(UserHandle.USER_SYSTEM); - if (newUserState != null) { - ArraySet<String> newEnabledHosts = newUserState.getEnabledHosts(); - DomainVerificationInternalUserState oldUserState = - oldSelectionStates.get(UserHandle.USER_SYSTEM); - - boolean linkHandlingAllowed = newUserState.isLinkHandlingAllowed(); - if (oldUserState == null) { - oldUserState = new DomainVerificationInternalUserState(UserHandle.USER_SYSTEM, - newEnabledHosts, linkHandlingAllowed); - oldSelectionStates.put(UserHandle.USER_SYSTEM, oldUserState); - } else { - oldUserState.addHosts(newEnabledHosts) - .setLinkHandlingAllowed(linkHandlingAllowed); + final int userStateSize = newSelectionStates.size(); + for (int index = 0; index < userStateSize; index++) { + int userId = newSelectionStates.keyAt(index); + DomainVerificationInternalUserState newUserState = newSelectionStates.valueAt(index); + if (newUserState != null) { + ArraySet<String> newEnabledHosts = newUserState.getEnabledHosts(); + DomainVerificationInternalUserState oldUserState = oldSelectionStates.get(userId); + boolean linkHandlingAllowed = newUserState.isLinkHandlingAllowed(); + if (oldUserState == null) { + oldSelectionStates.put(userId, new DomainVerificationInternalUserState(userId, + newEnabledHosts, linkHandlingAllowed)); + } else { + oldUserState.addHosts(newEnabledHosts) + .setLinkHandlingAllowed(linkHandlingAllowed); + } } } } diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt index 1097c45f8a9f..752024d6ad98 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt @@ -48,6 +48,8 @@ import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyLong import org.mockito.ArgumentMatchers.anyString +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream import java.util.UUID class DomainVerificationPackageTest { @@ -449,12 +451,141 @@ class DomainVerificationPackageTest { assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName) } + @Test + fun backupAndRestore() { + // This test acts as a proxy for true user restore through PackageManager, + // as that's much harder to test for real. + + val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE, SIGNATURE_ONE, listOf(DOMAIN_1, DOMAIN_2)) + val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO, SIGNATURE_TWO, + listOf(DOMAIN_1, DOMAIN_2, DOMAIN_3)) + val serviceBefore = makeService(pkg1, pkg2) + serviceBefore.addPackage(pkg1) + serviceBefore.addPackage(pkg2) + + serviceBefore.setStatus(pkg1.domainSetId, setOf(DOMAIN_1), STATE_SUCCESS) + serviceBefore.setDomainVerificationLinkHandlingAllowed(pkg1.getName(), false, 1) + serviceBefore.setUserSelection(pkg2.domainSetId, setOf(DOMAIN_2), true, 0) + serviceBefore.setUserSelection(pkg2.domainSetId, setOf(DOMAIN_3), true, 1) + + fun assertExpectedState(service: DomainVerificationService) { + service.assertState( + pkg1, userId = 0, hostToStateMap = mapOf( + DOMAIN_1 to DOMAIN_STATE_VERIFIED, + DOMAIN_2 to DOMAIN_STATE_NONE, + ) + ) + + service.assertState( + pkg1, userId = 1, linkHandingAllowed = false, hostToStateMap = mapOf( + DOMAIN_1 to DOMAIN_STATE_VERIFIED, + DOMAIN_2 to DOMAIN_STATE_NONE, + ) + ) + + service.assertState( + pkg2, userId = 0, hostToStateMap = mapOf( + DOMAIN_1 to DOMAIN_STATE_NONE, + DOMAIN_2 to DOMAIN_STATE_SELECTED, + DOMAIN_3 to DOMAIN_STATE_NONE + ) + ) + + service.assertState( + pkg2, userId = 1, hostToStateMap = mapOf( + DOMAIN_1 to DOMAIN_STATE_NONE, + DOMAIN_2 to DOMAIN_STATE_NONE, + DOMAIN_3 to DOMAIN_STATE_SELECTED, + ) + ) + } + + assertExpectedState(serviceBefore) + + val backupUser0 = ByteArrayOutputStream().use { + serviceBefore.writeSettings(Xml.resolveSerializer(it), true, 0) + it.toByteArray() + } + + val backupUser1 = ByteArrayOutputStream().use { + serviceBefore.writeSettings(Xml.resolveSerializer(it), true, 1) + it.toByteArray() + } + + val serviceAfter = makeService(pkg1, pkg2) + serviceAfter.addPackage(pkg1) + serviceAfter.addPackage(pkg2) + + // Check the state is default before the restoration applies + listOf(0, 1).forEach { + serviceAfter.assertState( + pkg1, userId = it, hostToStateMap = mapOf( + DOMAIN_1 to DOMAIN_STATE_NONE, + DOMAIN_2 to DOMAIN_STATE_NONE, + ) + ) + } + + listOf(0, 1).forEach { + serviceAfter.assertState( + pkg2, userId = it, hostToStateMap = mapOf( + DOMAIN_1 to DOMAIN_STATE_NONE, + DOMAIN_2 to DOMAIN_STATE_NONE, + DOMAIN_3 to DOMAIN_STATE_NONE, + ) + ) + } + + ByteArrayInputStream(backupUser1).use { + serviceAfter.restoreSettings(Xml.resolvePullParser(it)) + } + + // Assert user 1 was restored + serviceAfter.assertState( + pkg1, userId = 1, linkHandingAllowed = false, hostToStateMap = mapOf( + DOMAIN_1 to DOMAIN_STATE_VERIFIED, + DOMAIN_2 to DOMAIN_STATE_NONE, + ) + ) + + serviceAfter.assertState( + pkg2, userId = 1, hostToStateMap = mapOf( + DOMAIN_1 to DOMAIN_STATE_NONE, + DOMAIN_2 to DOMAIN_STATE_NONE, + DOMAIN_3 to DOMAIN_STATE_SELECTED, + ) + ) + + // User 0 has domain verified (since that's not user-specific) + serviceAfter.assertState( + pkg1, userId = 0, hostToStateMap = mapOf( + DOMAIN_1 to DOMAIN_STATE_VERIFIED, + DOMAIN_2 to DOMAIN_STATE_NONE, + ) + ) + + // But user 0 is missing any user selected state + serviceAfter.assertState( + pkg2, userId = 0, hostToStateMap = mapOf( + DOMAIN_1 to DOMAIN_STATE_NONE, + DOMAIN_2 to DOMAIN_STATE_NONE, + DOMAIN_3 to DOMAIN_STATE_NONE, + ) + ) + + ByteArrayInputStream(backupUser0).use { + serviceAfter.restoreSettings(Xml.resolvePullParser(it)) + } + + assertExpectedState(serviceAfter) + } + private fun DomainVerificationService.getInfo(pkgName: String) = getDomainVerificationInfo(pkgName) .also { assertThat(it).isNotNull() }!! - private fun DomainVerificationService.getUserState(pkgName: String) = - getDomainVerificationUserState(pkgName, USER_ID) + private fun DomainVerificationService.getUserState(pkgName: String, userId: Int = USER_ID) = + getDomainVerificationUserState(pkgName, userId) .also { assertThat(it).isNotNull() }!! private fun makeService(vararg pkgSettings: PackageSetting) = @@ -526,7 +657,23 @@ class DomainVerificationPackageTest { whenever(this.domainSetId) { domainSetId } whenever(getInstantApp(anyInt())) { false } whenever(firstInstallTime) { 0L } - whenever(readUserState(USER_ID)) { PackageUserState() } + whenever(readUserState(0)) { PackageUserState() } + whenever(readUserState(1)) { PackageUserState() } whenever(signatures) { arrayOf(Signature(signature)) } } + + private fun DomainVerificationService.assertState( + pkg: PackageSetting, + userId: Int, + linkHandingAllowed: Boolean = true, + hostToStateMap: Map<String, Int> + ) { + getUserState(pkg.getName(), userId).apply { + assertThat(this.packageName).isEqualTo(pkg.getName()) + assertThat(this.identifier).isEqualTo(pkg.domainSetId) + assertThat(this.isLinkHandlingAllowed).isEqualTo(linkHandingAllowed) + assertThat(this.user.identifier).isEqualTo(userId) + assertThat(this.hostToStateMap).containsExactlyEntriesIn(hostToStateMap) + } + } } diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt index 7ffbbf6af159..ad652dff901c 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt @@ -17,6 +17,7 @@ package com.android.server.pm.test.verify.domain import android.content.pm.verify.domain.DomainVerificationState +import android.os.UserHandle import android.util.ArrayMap import android.util.SparseArray import android.util.TypedXmlPullParser @@ -110,7 +111,8 @@ class DomainVerificationPersistenceTest { fun writeAndReadBackNormal() { val (attached, pending, restored) = mockWriteValues() val file = tempFolder.newFile().writeXml { - DomainVerificationPersistence.writeToXml(it, attached, pending, restored, null) + DomainVerificationPersistence.writeToXml(it, attached, pending, restored, + UserHandle.USER_ALL, null) } val xml = file.readText() @@ -128,7 +130,8 @@ class DomainVerificationPersistenceTest { fun writeAndReadBackWithSignature() { val (attached, pending, restored) = mockWriteValues() val file = tempFolder.newFile().writeXml { - DomainVerificationPersistence.writeToXml(it, attached, pending, restored) { + DomainVerificationPersistence.writeToXml(it, attached, pending, restored, + UserHandle.USER_ALL) { "SIGNATURE_$it" } } @@ -156,7 +159,8 @@ class DomainVerificationPersistenceTest { fun writeStateSignatureIfFunctionReturnsNull() { val (attached, pending, restored) = mockWriteValues { "SIGNATURE_$it" } val file = tempFolder.newFile().writeXml { - DomainVerificationPersistence.writeToXml(it, attached, pending, restored) { null } + DomainVerificationPersistence.writeToXml(it, attached, pending, restored, + UserHandle.USER_ALL) { null } } val (readActive, readRestored) = file.readXml { @@ -254,7 +258,7 @@ class DomainVerificationPersistenceTest { > <state/> <user-states> - <user-state userId="1" allowLinkHandling="false"> + <user-state userId="1"> <enabled-hosts> <host name="example-user1.com"/> <host name="example-user1.org"/> diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationTestUtils.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationTestUtils.kt index e1da727fb64f..00986f741eea 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationTestUtils.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationTestUtils.kt @@ -42,6 +42,11 @@ internal object DomainVerificationTestUtils { Function<String, PackageSetting?>, *>) .accept { block(it) } } + whenever(withPackageSettingsThrowing2<Exception, Exception>(any())) { + (arguments[0] as DomainVerificationManagerInternal.Connection.Throwing2Consumer< + Function<String, PackageSetting?>, *, *>) + .accept { block(it) } + } whenever(withPackageSettingsReturningThrowing<Any, Exception>(any())) { (arguments[0] as DomainVerificationManagerInternal.Connection.ThrowingFunction< Function<String, PackageSetting?>, *, *>) |