Merge changes I2cf2e739,I9e4bd242,Ic8b93a8c,Ibccde458,I04942e14, ... into sc-dev

* changes:
  Fix domain verify restore and add signature check
  Support serializing package signatures for domain verification state
  Add domain verification CTS to TEST_MAPPING
  Fix DomainVerificationService deadlock
  Check installed and enabled state for package domain approval
  Revoke domain user selection when approved through shell
This commit is contained in:
Winson Chiu
2021-04-09 17:05:50 +00:00
committed by Android (Google) Code Review
17 changed files with 1228 additions and 512 deletions

View File

@@ -32,6 +32,7 @@ import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.pm.overlay.OverlayPaths;
import android.content.pm.parsing.ParsingPackageRead;
import android.content.pm.parsing.component.ParsedMainComponent;
import android.os.BaseBundle;
import android.os.Debug;
@@ -53,7 +54,6 @@ import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
@@ -310,6 +310,20 @@ public class PackageUserState {
return result;
}
public boolean isPackageEnabled(@NonNull ParsingPackageRead pkg) {
switch (this.enabled) {
case COMPONENT_ENABLED_STATE_ENABLED:
return true;
case COMPONENT_ENABLED_STATE_DISABLED:
case COMPONENT_ENABLED_STATE_DISABLED_USER:
case COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED:
return false;
default:
case COMPONENT_ENABLED_STATE_DEFAULT:
return pkg.isEnabled();
}
}
public boolean isEnabled(ComponentInfo componentInfo, int flags) {
return isEnabled(componentInfo.applicationInfo.enabled, componentInfo.enabled,
componentInfo.name, flags);

View File

@@ -7,6 +7,12 @@
"include-filter": "com.android.server.pm.test.verify.domain"
}
]
},
{
"name": "CtsDomainVerificationDeviceTestCases"
},
{
"name": "CtsDomainVerificationHostTestCases"
}
]
}

View File

@@ -356,6 +356,7 @@ import com.android.internal.util.CollectionUtils;
import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.FunctionalUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.permission.persistence.RuntimePermissionsPersistence;
@@ -1686,9 +1687,8 @@ public class PackageManagerService extends IPackageManager.Stub
private final DomainVerificationConnection mDomainVerificationConnection =
new DomainVerificationConnection();
private class DomainVerificationConnection implements
DomainVerificationService.Connection, DomainVerificationProxyV1.Connection,
DomainVerificationProxyV2.Connection {
private class DomainVerificationConnection implements DomainVerificationService.Connection,
DomainVerificationProxyV1.Connection, DomainVerificationProxyV2.Connection {
@Override
public void scheduleWriteSettings() {
@@ -1732,22 +1732,77 @@ public class PackageManagerService extends IPackageManager.Stub
return callingUid == getPackageUid(packageName, 0, callingUserId);
}
@Nullable
@Override
public PackageSetting getPackageSettingLocked(@NonNull String pkgName) {
return PackageManagerService.this.getPackageSetting(pkgName);
}
@Nullable
@Override
public AndroidPackage getPackageLocked(@NonNull String pkgName) {
return PackageManagerService.this.getPackage(pkgName);
}
@Nullable
@Override
public AndroidPackage getPackage(@NonNull String packageName) {
return getPackageLocked(packageName);
return PackageManagerService.this.getPackage(packageName);
}
@NonNull
@Override
public void withPackageSettings(@NonNull Consumer<Function<String, PackageSetting>> block) {
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> Output withPackageSettingsReturning(
@NonNull FunctionalUtils.ThrowingFunction<Function<String, PackageSetting>, Output>
block) {
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) {
return block.apply(snapshot::getPackageSetting);
}
} else {
return block.apply(snapshot::getPackageSetting);
}
}
@Override
public <ExceptionType extends Exception> void withPackageSettingsThrowing(
@NonNull ThrowingConsumer<Function<String, PackageSetting>, ExceptionType> block)
throws ExceptionType {
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 {
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) {
return block.apply(snapshot::getPackageSetting);
}
} else {
return block.apply(snapshot::getPackageSetting);
}
}
@Override
@@ -2012,7 +2067,7 @@ public class PackageManagerService extends IPackageManager.Stub
// Cached attributes. The names in this class are the same as the
// names in PackageManagerService; see that class for documentation.
private final Settings mSettings;
protected final Settings mSettings;
private final WatchedSparseIntArray mIsolatedOwners;
private final WatchedArrayMap<String, AndroidPackage> mPackages;
private final WatchedArrayMap<ComponentName, ParsedInstrumentation>
@@ -4669,6 +4724,17 @@ public class PackageManagerService extends IPackageManager.Stub
mLock = mService.mLock;
}
/**
* Explicilty snapshot {@link Settings#mPackages} for cases where the caller must not lock
* in order to get package data. It is expected that the caller locks itself to be able
* to block on changes to the package data and bring itself up to date once the change
* propagates to it. Use with heavy caution.
* @return
*/
private Map<String, PackageSetting> snapshotPackageSettings() {
return mSettings.snapshot().mPackages;
}
public @NonNull List<ResolveInfo> queryIntentServicesInternalBody(Intent intent,
String resolvedType, int flags, int userId, int callingUid,
String instantAppPkgName) {
@@ -4800,7 +4866,7 @@ public class PackageManagerService extends IPackageManager.Stub
// Compute read-only functions, based on live data. This attribute may be modified multiple
// times during the PackageManagerService constructor but it should not be modified thereafter.
private Computer mLiveComputer;
private ComputerLocked mLiveComputer;
// A lock-free cache for frequently called functions.
private volatile Computer mSnapshotComputer;
// If true, the snapshot is invalid (stale). The attribute is static since it may be
@@ -21760,6 +21826,7 @@ public class PackageManagerService extends IPackageManager.Stub
clearPackagePreferredActivities(ps.name, nextUserId);
mPermissionManager.onPackageUninstalled(ps.name, ps.appId, pkg, sharedUserPkgs,
nextUserId);
mDomainVerificationManager.clearPackageForUser(ps.name, nextUserId);
}
if (outInfo != null) {

View File

@@ -28,6 +28,7 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.verify.domain.DomainVerificationInfo;
import android.content.pm.verify.domain.DomainVerificationManager;
import android.content.pm.verify.domain.DomainVerificationState;
import android.os.Binder;
import android.os.UserHandle;
import android.util.IndentingPrintWriter;
@@ -35,8 +36,10 @@ import android.util.Pair;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import com.android.internal.util.FunctionalUtils;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.PackageSetting;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.Settings;
import com.android.server.pm.verify.domain.models.DomainVerificationPkgState;
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
@@ -46,6 +49,7 @@ import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.Function;
public interface DomainVerificationManagerInternal {
@@ -108,9 +112,10 @@ public interface DomainVerificationManagerInternal {
int APPROVAL_LEVEL_INSTANT_APP = 5;
/**
* Defines the possible values for {@link #approvalLevelForDomain(PackageSetting, Intent, int)}
* which sorts packages by approval priority. A higher numerical value means the package should
* override all lower values. This means that comparison using less/greater than IS valid.
* Defines the possible values for
* {@link #approvalLevelForDomain(PackageSetting, Intent, List, int, int)} which sorts packages
* by approval priority. A higher numerical value means the package should override all lower
* values. This means that comparison using less/greater than IS valid.
*
* Negative values are possible, although not implemented, reserved if explicit disable of a
* package for a domain needs to be tracked.
@@ -184,7 +189,7 @@ public interface DomainVerificationManagerInternal {
/**
* 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 DomainVerificationManager#STATE_SUCCESS}
* {@link #generateNewId()}. This will preserve {@link DomainVerificationState#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.
* <p>
@@ -227,6 +232,11 @@ public interface DomainVerificationManagerInternal {
*/
void clearPackage(@NonNull String packageName);
/**
* Remove all state for the given package for the given user.
*/
void clearPackageForUser(@NonNull String packageName, @UserIdInt int userId);
/**
* Delete all the state for a user. This can be because the user has been removed from the
* device, or simply that the state for a user should be deleted.
@@ -271,7 +281,7 @@ public interface DomainVerificationManagerInternal {
/**
* Until the legacy APIs are entirely removed, returns the legacy state from the previously
* written info stored in {@link com.android.server.pm.Settings}.
* written info stored in {@link Settings}.
*/
int getLegacyState(@NonNull String packageName, @UserIdInt int userId);
@@ -283,15 +293,12 @@ public interface DomainVerificationManagerInternal {
* @param userId the specific user to print, or null to skip printing user selection
* states, supports {@link android.os.UserHandle#USER_ALL}
* @param pkgSettingFunction the method by which to retrieve package data; if this is called
* from {@link com.android.server.pm.PackageManagerService}, it is
* expected to pass in the snapshot of {@link PackageSetting} objects,
* or if null is passed, the manager may decide to lock {@link
* com.android.server.pm.PackageManagerService} through {@link
* Connection#getPackageSettingLocked(String)}
* from {@link PackageManagerService}, it is
* expected to pass in the snapshot of {@link PackageSetting} objects
*/
void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName,
@Nullable @UserIdInt Integer userId,
@Nullable Function<String, PackageSetting> pkgSettingFunction)
@NonNull Function<String, PackageSetting> pkgSettingFunction)
throws NameNotFoundException;
@NonNull
@@ -363,17 +370,46 @@ public interface DomainVerificationManagerInternal {
*/
void schedule(int code, @Nullable Object object);
// TODO(b/178733426): Make DomainVerificationService PMS snapshot aware so it can avoid
// locking package state at all. This can be as simple as removing this method in favor of
// accepting a PackageSetting function in at every method call, although should probably
// be abstracted to a wrapper class.
@Nullable
PackageSetting getPackageSettingLocked(@NonNull String pkgName);
/**
* Run a function block that requires access to {@link PackageSetting} data. This will
* ensure the {@link PackageManagerService} is taken before
* {@link DomainVerificationManagerInternal}'s lock is taken to avoid deadlock.
*/
void withPackageSettings(@NonNull Consumer<Function<String, PackageSetting>> block);
@Nullable
AndroidPackage getPackageLocked(@NonNull String pkgName);
/**
* Variant which returns a value to the caller.
* @see #withPackageSettings(Consumer)
*/
<Output> Output withPackageSettingsReturning(
@NonNull FunctionalUtils.ThrowingFunction<Function<String, PackageSetting>, Output>
block);
/**
* Variant which throws.
* @see #withPackageSettings(Consumer)
*/
<ExceptionType extends Exception> void withPackageSettingsThrowing(
@NonNull ThrowingConsumer<Function<String, PackageSetting>, ExceptionType> block)
throws ExceptionType;
/**
* Variant which returns a value to the caller and throws.
* @see #withPackageSettings(Consumer)
*/
<Output, ExceptionType extends Exception> Output withPackageSettingsReturningThrowing(
@NonNull ThrowingFunction<Function<String, PackageSetting>, Output, ExceptionType>
block) throws ExceptionType;
@UserIdInt
int[] getAllUserIds();
interface ThrowingConsumer<Input, ExceptionType extends Exception> {
void accept(Input input) throws ExceptionType;
}
interface ThrowingFunction<Input, Output, ExceptionType extends Exception> {
Output apply(Input input) throws ExceptionType;
}
}
}

View File

@@ -18,10 +18,12 @@ package com.android.server.pm.verify.domain;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.Signature;
import android.content.pm.verify.domain.DomainVerificationState;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.PackageUtils;
import android.util.SparseArray;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
@@ -36,6 +38,7 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.Collection;
import java.util.UUID;
import java.util.function.Function;
public class DomainVerificationPersistence {
@@ -49,6 +52,7 @@ public class DomainVerificationPersistence {
private static final String ATTR_PACKAGE_NAME = "packageName";
private static final String ATTR_ID = "id";
private static final String ATTR_HAS_AUTO_VERIFY_DOMAINS = "hasAutoVerifyDomains";
private static final String ATTR_SIGNATURE = "signature";
private static final String TAG_USER_STATES = "user-states";
public static final String TAG_USER_STATE = "user-state";
@@ -62,10 +66,18 @@ public class DomainVerificationPersistence {
public static final String ATTR_NAME = "name";
public static final String ATTR_STATE = "state";
/**
* @param pkgNameToSignature Converts package name to a string representation of its signature.
* Usually this is the SHA-256 hash from
* {@link PackageUtils#computeSignaturesSha256Digest(Signature[])},
* but can be an arbitrary string for testing purposes. Pass non-null
* to write out signatures, or null to ignore.
*/
public static void writeToXml(@NonNull TypedXmlSerializer xmlSerializer,
@NonNull DomainVerificationStateMap<DomainVerificationPkgState> attached,
@NonNull ArrayMap<String, DomainVerificationPkgState> pending,
@NonNull ArrayMap<String, DomainVerificationPkgState> restored) throws IOException {
@NonNull ArrayMap<String, DomainVerificationPkgState> restored,
@Nullable Function<String, String> pkgNameToSignature) throws IOException {
try (SettingsXml.Serializer serializer = SettingsXml.serializer(xmlSerializer)) {
try (SettingsXml.WriteSection ignored = serializer.startSection(
TAG_DOMAIN_VERIFICATIONS)) {
@@ -86,25 +98,26 @@ public class DomainVerificationPersistence {
}
try (SettingsXml.WriteSection activeSection = serializer.startSection(TAG_ACTIVE)) {
writePackageStates(activeSection, active);
writePackageStates(activeSection, active, pkgNameToSignature);
}
try (SettingsXml.WriteSection restoredSection = serializer.startSection(
TAG_RESTORED)) {
writePackageStates(restoredSection, restored.values());
writePackageStates(restoredSection, restored.values(), pkgNameToSignature);
}
}
}
}
private static void writePackageStates(@NonNull SettingsXml.WriteSection section,
@NonNull Collection<DomainVerificationPkgState> states) throws IOException {
@NonNull Collection<DomainVerificationPkgState> states,
@Nullable Function<String, String> pkgNameToSignature) throws IOException {
if (states.isEmpty()) {
return;
}
for (DomainVerificationPkgState state : states) {
writePkgStateToXml(section, state);
writePkgStateToXml(section, state, pkgNameToSignature);
}
}
@@ -146,11 +159,12 @@ public class DomainVerificationPersistence {
* been entered.
*/
@Nullable
public static DomainVerificationPkgState createPkgStateFromXml(
private static DomainVerificationPkgState createPkgStateFromXml(
@NonNull SettingsXml.ReadSection section) {
String packageName = section.getString(ATTR_PACKAGE_NAME);
String idString = section.getString(ATTR_ID);
boolean hasAutoVerifyDomains = section.getBoolean(ATTR_HAS_AUTO_VERIFY_DOMAINS);
String signature = section.getString(ATTR_SIGNATURE);
if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(idString)) {
return null;
}
@@ -172,7 +186,7 @@ public class DomainVerificationPersistence {
}
return new DomainVerificationPkgState(packageName, id, hasAutoVerifyDomains, stateMap,
userStates);
userStates, signature);
}
private static void readUserStates(@NonNull SettingsXml.ReadSection section,
@@ -196,14 +210,26 @@ public class DomainVerificationPersistence {
}
}
public static void writePkgStateToXml(@NonNull SettingsXml.WriteSection parentSection,
@NonNull DomainVerificationPkgState pkgState) throws IOException {
private static void writePkgStateToXml(@NonNull SettingsXml.WriteSection parentSection,
@NonNull DomainVerificationPkgState pkgState,
@Nullable Function<String, String> pkgNameToSignature) throws IOException {
String packageName = pkgState.getPackageName();
String signature = pkgNameToSignature == null
? null : pkgNameToSignature.apply(packageName);
if (signature == null) {
// If a package isn't available to get its signature, fallback to the previously stored
// result, which can occur if the package has been marked for restore but hasn't
// been installed on the new device yet.
signature = pkgState.getBackupSignatureHash();
}
try (SettingsXml.WriteSection ignored =
parentSection.startSection(TAG_PACKAGE_STATE)
.attribute(ATTR_PACKAGE_NAME, pkgState.getPackageName())
.attribute(ATTR_PACKAGE_NAME, packageName)
.attribute(ATTR_ID, pkgState.getId().toString())
.attribute(ATTR_HAS_AUTO_VERIFY_DOMAINS,
pkgState.isHasAutoVerifyDomains())) {
pkgState.isHasAutoVerifyDomains())
.attribute(ATTR_SIGNATURE, signature)) {
writeStateMap(parentSection, pkgState.getStateMap());
writeUserStates(parentSection, pkgState.getUserStates());
}
@@ -245,7 +271,7 @@ public class DomainVerificationPersistence {
* entered.
*/
@Nullable
public static DomainVerificationInternalUserState createUserStateFromXml(
private static DomainVerificationInternalUserState createUserStateFromXml(
@NonNull SettingsXml.ReadSection section) {
int userId = section.getInt(ATTR_USER_ID);
if (userId == -1) {
@@ -274,7 +300,7 @@ public class DomainVerificationPersistence {
}
}
public static void writeUserStateToXml(@NonNull SettingsXml.WriteSection parentSection,
private static void writeUserStateToXml(@NonNull SettingsXml.WriteSection parentSection,
@NonNull DomainVerificationInternalUserState userState) throws IOException {
try (SettingsXml.WriteSection section =
parentSection.startSection(TAG_USER_STATE)

View File

@@ -36,6 +36,7 @@ import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.function.Function;
class DomainVerificationSettings {
@@ -68,18 +69,19 @@ class DomainVerificationSettings {
public void writeSettings(@NonNull TypedXmlSerializer xmlSerializer,
@NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState)
throws IOException {
@NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState,
@NonNull Function<String, String> pkgSignatureFunction) throws IOException {
synchronized (mLock) {
DomainVerificationPersistence.writeToXml(xmlSerializer, liveState,
mPendingPkgStates, mRestoredPkgStates);
mPendingPkgStates, mRestoredPkgStates, 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)} on the same device setup.
* #writeSettings(TypedXmlSerializer, DomainVerificationStateMap, Function)} on the same device
* setup.
*/
public void readSettings(@NonNull TypedXmlPullParser parser,
@NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState)
@@ -155,16 +157,20 @@ class DomainVerificationSettings {
// mergePkgState runs, without the merge part.
ArrayMap<String, Integer> stateMap = newState.getStateMap();
int size = stateMap.size();
for (int index = 0; index < size; index++) {
for (int index = size - 1; index >= 0; index--) {
Integer stateInteger = stateMap.valueAt(index);
if (stateInteger != null) {
int state = stateInteger;
if (state == DomainVerificationState.STATE_SUCCESS
|| state == DomainVerificationState.STATE_RESTORED) {
stateMap.setValueAt(index, state);
stateMap.setValueAt(index, DomainVerificationState.STATE_RESTORED);
} else {
stateMap.removeAt(index);
}
}
}
mRestoredPkgStates.put(pkgName, newState);
}
}
}
@@ -241,17 +247,41 @@ class DomainVerificationSettings {
}
}
public void removeUser(@UserIdInt int userId) {
int pendingSize = mPendingPkgStates.size();
for (int index = 0; index < pendingSize; index++) {
mPendingPkgStates.valueAt(index).removeUser(userId);
public void removePackage(@NonNull String packageName) {
synchronized (mLock) {
mPendingPkgStates.remove(packageName);
mRestoredPkgStates.remove(packageName);
}
}
// TODO(b/170746586): Restored assumes user IDs match, which is probably not the case
// on a new device
int restoredSize = mRestoredPkgStates.size();
for (int index = 0; index < restoredSize; index++) {
mRestoredPkgStates.valueAt(index).removeUser(userId);
public void removePackageForUser(@NonNull String packageName, @UserIdInt int userId) {
synchronized (mLock) {
final DomainVerificationPkgState pendingPkgState = mPendingPkgStates.get(packageName);
if (pendingPkgState != null) {
pendingPkgState.removeUser(userId);
}
// TODO(b/170746586): Restored assumes user IDs match, which is probably not the case
// on a new device
final DomainVerificationPkgState restoredPkgState = mRestoredPkgStates.get(packageName);
if (restoredPkgState != null) {
restoredPkgState.removeUser(userId);
}
}
}
public void removeUser(@UserIdInt int userId) {
synchronized (mLock) {
int pendingSize = mPendingPkgStates.size();
for (int index = 0; index < pendingSize; index++) {
mPendingPkgStates.valueAt(index).removeUser(userId);
}
// TODO(b/170746586): Restored assumes user IDs match, which is probably not the case
// on a new device
int restoredSize = mRestoredPkgStates.size();
for (int index = 0; index < restoredSize; index++) {
mRestoredPkgStates.valueAt(index).removeUser(userId);
}
}
}

View File

@@ -98,7 +98,7 @@ public class DomainVerificationShell {
pw.println(" must be declared by the package for this to work. This command will not");
pw.println(" report a failure for domains that could not be applied.");
pw.println(" --user <USER_ID>: the user to change selections for");
pw.println(" --package <PACKAGE>: the package to set, or \"all\" to set all packages");
pw.println(" --package <PACKAGE>: the package to set");
pw.println(" <ENABLED>: whether or not to approve the domain");
pw.println(" <DOMAINS>: space separated list of domains to change, or \"all\" to");
pw.println(" change every domain.");
@@ -226,8 +226,6 @@ public class DomainVerificationShell {
if (TextUtils.isEmpty(packageName)) {
commandHandler.getErrPrintWriter().println("Error: no package specified");
return false;
} else if (packageName.equalsIgnoreCase("all")) {
packageName = null;
}
if (userId == null) {
@@ -478,13 +476,15 @@ public class DomainVerificationShell {
* Variant for use by PackageManagerShellCommand to allow the system/developer to override
* the state for a domain.
*
* @param packageName the package whose state to change, or all packages if non is
* specified
* If an approval fails because of a higher level owner, this method will silently skip the
* domain.
*
* @param packageName the package whose state to change
* @param enabled whether the domain is now approved by the user
* @param domains the set of domains to change, or null to affect all domains
*/
void setDomainVerificationUserSelectionInternal(@UserIdInt int userId,
@Nullable String packageName, boolean enabled, @Nullable ArraySet<String> domains)
@NonNull String packageName, boolean enabled, @Nullable ArraySet<String> domains)
throws PackageManager.NameNotFoundException;
/**

View File

@@ -7,6 +7,12 @@
"include-filter": "com.android.server.pm.test.verify.domain"
}
]
},
{
"name": "CtsDomainVerificationDeviceTestCases"
},
{
"name": "CtsDomainVerificationHostTestCases"
}
]
}

View File

@@ -19,6 +19,7 @@ package com.android.server.pm.verify.domain.models;
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.util.ArrayMap;
import android.util.SparseArray;
@@ -63,15 +64,28 @@ public class DomainVerificationPkgState {
@NonNull
private final SparseArray<DomainVerificationInternalUserState> mUserStates;
/**
* If previously recorded, the SHA-256 signing cert digest of the package to attach to.
* When doing restoration of a previously backed up state, if the signature does not
* match the package being scanned/installed on device, it will be rejected.
*
* It's assumed the domain verification agent will eventually re-verify this domain
* and revoke if necessary.
*
* @see android.util.PackageUtils#computeSignaturesSha256Digest(Signature[])
*/
@Nullable
private final String mBackupSignatureHash;
public DomainVerificationPkgState(@NonNull String packageName, @NonNull UUID id,
boolean hasAutoVerifyDomains) {
this(packageName, id, hasAutoVerifyDomains, new ArrayMap<>(0), new SparseArray<>(0));
this(packageName, id, hasAutoVerifyDomains, new ArrayMap<>(0), new SparseArray<>(0), null);
}
public DomainVerificationPkgState(@NonNull DomainVerificationPkgState pkgState,
@NonNull UUID id, boolean hasAutoVerifyDomains) {
this(pkgState.getPackageName(), id, hasAutoVerifyDomains, pkgState.getStateMap(),
pkgState.getUserStates());
pkgState.getUserStates(), null);
}
@Nullable
@@ -109,7 +123,7 @@ public class DomainVerificationPkgState {
// Code below generated by codegen v1.0.22.
// Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -137,6 +151,13 @@ public class DomainVerificationPkgState {
*
* TODO(b/159952358): Hide the state map entirely from the caller, to allow optimizations,
* such as storing no state when the package is marked as a linked app in SystemConfig.
* @param backupSignatureHash
* If previously recorded, the SHA-256 signing cert digest of the package to attach to.
* When doing restoration of a previously backed up state, if the signature does not
* match the package being scanned/installed on device, it will be rejected.
*
* It's assumed the domain verification agent will eventually re-verify this domain
* and revoke if necessary.
*/
@DataClass.Generated.Member
public DomainVerificationPkgState(
@@ -144,7 +165,8 @@ public class DomainVerificationPkgState {
@NonNull UUID id,
boolean hasAutoVerifyDomains,
@NonNull ArrayMap<String,Integer> stateMap,
@NonNull SparseArray<DomainVerificationInternalUserState> userStates) {
@NonNull SparseArray<DomainVerificationInternalUserState> userStates,
@Nullable String backupSignatureHash) {
this.mPackageName = packageName;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mPackageName);
@@ -158,6 +180,7 @@ public class DomainVerificationPkgState {
this.mUserStates = userStates;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mUserStates);
this.mBackupSignatureHash = backupSignatureHash;
// onConstructed(); // You can define this method to get a callback
}
@@ -201,6 +224,21 @@ public class DomainVerificationPkgState {
return mUserStates;
}
/**
* If previously recorded, the SHA-256 signing cert digest of the package to attach to.
* When doing restoration of a previously backed up state, if the signature does not
* match the package being scanned/installed on device, it will be rejected.
*
* It's assumed the domain verification agent will eventually re-verify this domain
* and revoke if necessary.
*
* @see android.util.PackageUtils#computeSignaturesSha256Digest(Signature[])
*/
@DataClass.Generated.Member
public @Nullable String getBackupSignatureHash() {
return mBackupSignatureHash;
}
@Override
@DataClass.Generated.Member
public String toString() {
@@ -212,7 +250,8 @@ public class DomainVerificationPkgState {
"id = " + mId + ", " +
"hasAutoVerifyDomains = " + mHasAutoVerifyDomains + ", " +
"stateMap = " + mStateMap + ", " +
"userStates = " + mUserStates +
"userStates = " + mUserStates + ", " +
"backupSignatureHash = " + mBackupSignatureHash +
" }";
}
@@ -233,7 +272,8 @@ public class DomainVerificationPkgState {
&& Objects.equals(mId, that.mId)
&& mHasAutoVerifyDomains == that.mHasAutoVerifyDomains
&& Objects.equals(mStateMap, that.mStateMap)
&& userStatesEquals(that.mUserStates);
&& userStatesEquals(that.mUserStates)
&& Objects.equals(mBackupSignatureHash, that.mBackupSignatureHash);
}
@Override
@@ -248,14 +288,15 @@ public class DomainVerificationPkgState {
_hash = 31 * _hash + Boolean.hashCode(mHasAutoVerifyDomains);
_hash = 31 * _hash + Objects.hashCode(mStateMap);
_hash = 31 * _hash + userStatesHashCode();
_hash = 31 * _hash + Objects.hashCode(mBackupSignatureHash);
return _hash;
}
@DataClass.Generated(
time = 1614818362549L,
codegenVersion = "1.0.22",
time = 1617315369614L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java",
inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate @android.annotation.NonNull java.util.UUID mId\nprivate final boolean mHasAutoVerifyDomains\nprivate final @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.Integer> mStateMap\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState> mUserStates\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState getUserState(int)\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState getOrCreateUserState(int)\npublic void setId(java.util.UUID)\npublic void removeUser(int)\npublic void removeAllUsers()\nprivate int userStatesHashCode()\nprivate boolean userStatesEquals(android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState>)\nclass DomainVerificationPkgState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate @android.annotation.NonNull java.util.UUID mId\nprivate final boolean mHasAutoVerifyDomains\nprivate final @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.Integer> mStateMap\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState> mUserStates\nprivate final @android.annotation.Nullable java.lang.String mBackupSignatureHash\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState getUserState(int)\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState getOrCreateUserState(int)\npublic void removeUser(int)\npublic void removeAllUsers()\nprivate int userStatesHashCode()\nprivate boolean userStatesEquals(android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState>)\nclass DomainVerificationPkgState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}

View File

@@ -31,6 +31,7 @@ import android.util.SparseArray
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.pm.PackageSetting
import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.pm.test.verify.domain.DomainVerificationTestUtils.mockPackageSettings
import com.android.server.pm.verify.domain.DomainVerificationEnforcer
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
import com.android.server.pm.verify.domain.DomainVerificationService
@@ -98,10 +99,13 @@ class DomainVerificationEnforcerTest {
mockThrowOnUnmocked {
whenever(callingUid) { callingUidInt.get() }
whenever(callingUserId) { callingUserIdInt.get() }
whenever(getPackageSettingLocked(VISIBLE_PKG)) { visiblePkgSetting }
whenever(getPackageLocked(VISIBLE_PKG)) { visiblePkg }
whenever(getPackageSettingLocked(INVISIBLE_PKG)) { invisiblePkgSetting }
whenever(getPackageLocked(INVISIBLE_PKG)) { invisiblePkg }
mockPackageSettings {
when (it) {
VISIBLE_PKG -> visiblePkgSetting
INVISIBLE_PKG -> invisiblePkgSetting
else -> null
}
}
whenever(schedule(anyInt(), any()))
whenever(scheduleWriteSettings())
whenever(filterAppAccess(eq(VISIBLE_PKG), anyInt(), anyInt())) { false }
@@ -290,6 +294,7 @@ class DomainVerificationEnforcerTest {
fun mockPkg(packageName: String) = mockThrowOnUnmocked<AndroidPackage> {
whenever(this.packageName) { packageName }
whenever(targetSdkVersion) { Build.VERSION_CODES.S }
whenever(isEnabled) { true }
whenever(activities) {
listOf(
ParsedActivity().apply {
@@ -330,11 +335,8 @@ class DomainVerificationEnforcerTest {
whenever(getName()) { packageName }
whenever(getPkg()) { mockPkg(packageName) }
whenever(this.domainSetId) { domainSetId }
whenever(userState) {
SparseArray<PackageUserState>().apply {
this[0] = PackageUserState()
}
}
whenever(readUserState(0)) { PackageUserState() }
whenever(readUserState(1)) { PackageUserState() }
whenever(getInstantApp(anyInt())) { false }
}
}

View File

@@ -19,6 +19,7 @@ package com.android.server.pm.test.verify.domain
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.PackageUserState
import android.content.pm.parsing.component.ParsedActivity
import android.content.pm.parsing.component.ParsedIntentInfo
import android.content.pm.verify.domain.DomainVerificationInfo
@@ -31,6 +32,7 @@ import android.os.Process
import android.util.ArraySet
import com.android.server.pm.PackageSetting
import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.pm.test.verify.domain.DomainVerificationTestUtils.mockPackageSettings
import com.android.server.pm.verify.domain.DomainVerificationService
import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.whenever
@@ -41,6 +43,7 @@ import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.ArgumentMatchers.anyString
import java.util.UUID
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.test.assertFailsWith
class DomainVerificationManagerApiTest {
@@ -267,7 +270,16 @@ class DomainVerificationManagerApiTest {
@Test
fun getOwnersForDomain() {
val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2))
val pkg1User0Enabled = AtomicBoolean(true)
val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2), pkgUserState0 = {
mockThrowOnUnmocked {
whenever(isPackageEnabled(any())) {
pkg1User0Enabled.get()
}
installed = true
}
})
val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO, listOf(DOMAIN_1, DOMAIN_2))
val service = makeService(pkg1, pkg2).apply {
@@ -276,9 +288,20 @@ class DomainVerificationManagerApiTest {
assertThat(service.getOwnersForDomain(DOMAIN_1, 0)).isEmpty()
service.setStatus(pkg1.domainSetId, setOf(DOMAIN_1), DomainVerificationInfo.STATE_SUCCESS)
service.setStatus(pkg2.domainSetId, setOf(DOMAIN_1), DomainVerificationInfo.STATE_SUCCESS)
assertThat(
service.setStatus(
pkg1.domainSetId,
setOf(DOMAIN_1),
DomainVerificationInfo.STATE_SUCCESS
)
).isEqualTo(DomainVerificationManager.STATUS_OK)
assertThat(
service.setStatus(
pkg2.domainSetId,
setOf(DOMAIN_1),
DomainVerificationInfo.STATE_SUCCESS
)
).isEqualTo(DomainVerificationManager.STATUS_OK)
service.setUserSelection(pkg1.domainSetId, setOf(DOMAIN_2), true, 0)
@@ -295,15 +318,65 @@ class DomainVerificationManagerApiTest {
assertThat(it.single().packageName).isEqualTo(pkg1.getName())
assertThat(it.single().isOverrideable).isEqualTo(true)
}
assertThat(service.getOwnersForDomain(DOMAIN_2, 1)).isEmpty()
assertThat(service.getOwnersForDomain(DOMAIN_2, 1)).isEmpty()
service.setUserSelection(pkg1.domainSetId, setOf(DOMAIN_2), true, 1)
service.getOwnersForDomain(DOMAIN_2, 1).let {
assertThat(it).hasSize(1)
assertThat(it.single().packageName).isEqualTo(pkg1.getName())
assertThat(it.single().isOverrideable).isEqualTo(true)
}
// "Uninstall" the package from user 0 and ensure it's stripped from the results
pkg1User0Enabled.set(false)
service.clearPackageForUser(pkg1.getName(), 0)
service.getOwnersForDomain(DOMAIN_1, 0).let {
assertThat(it).hasSize(1)
assertThat(it.single().packageName).isEqualTo(pkg2.getName())
assertThat(it.single().isOverrideable).isEqualTo(false)
}
// Domain 2 user selection gone for user 0
assertThat(service.getOwnersForDomain(DOMAIN_2, 0)).isEmpty()
// Domain 2 user selection still around for user 1
service.getOwnersForDomain(DOMAIN_2, 1).let {
assertThat(it).hasSize(1)
assertThat(it.single().packageName).isEqualTo(pkg1.getName())
assertThat(it.single().isOverrideable).isEqualTo(true)
}
// Now assert for user 1 that it was unaffected by the change to user 0
service.getOwnersForDomain(DOMAIN_1, 1).let {
assertThat(it).hasSize(2)
assertThat(it[0].packageName).isEqualTo(pkg1.getName())
assertThat(it[0].isOverrideable).isEqualTo(false)
assertThat(it[1].packageName).isEqualTo(pkg2.getName())
assertThat(it[1].isOverrideable).isEqualTo(false)
}
service.setUserSelection(pkg1.domainSetId, setOf(DOMAIN_2), true, 0)
service.getOwnersForDomain(DOMAIN_2, 1).let {
assertThat(it).hasSize(1)
assertThat(it.single().packageName).isEqualTo(pkg1.getName())
assertThat(it.single().isOverrideable).isEqualTo(true)
}
// "Reinstall" the package to user 0
pkg1User0Enabled.set(false)
// This state should have been cleared when the package was uninstalled
assertThat(service.getOwnersForDomain(DOMAIN_2, 0)).isEmpty()
// Other package unaffected
service.setUserSelection(pkg2.domainSetId, setOf(DOMAIN_2), true, 0)
service.getOwnersForDomain(DOMAIN_2, 0).let {
assertThat(it).hasSize(1)
assertThat(it.single().packageName).isEqualTo(pkg2.getName())
assertThat(it.single().isOverrideable).isEqualTo(true)
}
assertThat(service.getOwnersForDomain(DOMAIN_2, 1)).isEmpty()
}
@Test
@@ -373,21 +446,23 @@ class DomainVerificationManagerApiTest {
whenever(callingUid) { Process.ROOT_UID }
whenever(callingUserId) { 0 }
whenever(getPackageSettingLocked(anyString())) {
pkgSettingFunction(arguments[0] as String)
}
whenever(getPackageLocked(anyString())) {
pkgSettingFunction(arguments[0] as String)?.getPkg()
mockPackageSettings {
pkgSettingFunction(it)
}
})
}
private fun mockPkgSetting(pkgName: String, domainSetId: UUID, domains: List<String> = listOf(
DOMAIN_1, DOMAIN_2
)) = mockThrowOnUnmocked<PackageSetting> {
private fun mockPkgSetting(
pkgName: String,
domainSetId: UUID,
domains: List<String> = listOf(DOMAIN_1, DOMAIN_2),
pkgUserState0: PackageSetting.() -> PackageUserState = { PackageUserState() },
pkgUserState1: PackageSetting.() -> PackageUserState = { PackageUserState() }
) = mockThrowOnUnmocked<PackageSetting> {
val pkg = mockThrowOnUnmocked<AndroidPackage> {
whenever(packageName) { pkgName }
whenever(targetSdkVersion) { Build.VERSION_CODES.S }
whenever(isEnabled) { true }
val activityList = listOf(
ParsedActivity().apply {
@@ -416,6 +491,8 @@ class DomainVerificationManagerApiTest {
whenever(this.domainSetId) { domainSetId }
whenever(getInstantApp(anyInt())) { false }
whenever(firstInstallTime) { 0L }
whenever(readUserState(0)) { pkgUserState0() }
whenever(readUserState(1)) { pkgUserState1() }
}
fun DomainVerificationService.addPackages(vararg pkgSettings: PackageSetting) =

View File

@@ -18,8 +18,11 @@ package com.android.server.pm.test.verify.domain
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.PackageUserState
import android.content.pm.Signature
import android.content.pm.parsing.component.ParsedActivity
import android.content.pm.parsing.component.ParsedIntentInfo
import android.content.pm.verify.domain.DomainVerificationInfo.STATE_MODIFIABLE_VERIFIED
import android.content.pm.verify.domain.DomainVerificationInfo.STATE_NO_RESPONSE
import android.content.pm.verify.domain.DomainVerificationInfo.STATE_SUCCESS
import android.content.pm.verify.domain.DomainVerificationInfo.STATE_UNMODIFIABLE
@@ -35,12 +38,12 @@ import android.util.ArraySet
import android.util.Xml
import com.android.server.pm.PackageSetting
import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.pm.test.verify.domain.DomainVerificationTestUtils.mockPackageSettings
import com.android.server.pm.verify.domain.DomainVerificationService
import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.mockito.ArgumentMatchers
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyLong
@@ -54,6 +57,10 @@ class DomainVerificationPackageTest {
private const val PKG_TWO = "com.test.two"
private val UUID_ONE = UUID.fromString("1b041c96-8d37-4932-a858-561bfac5947c")
private val UUID_TWO = UUID.fromString("a3389c16-7f9f-4e86-85e3-500d1249c74c")
private const val SIGNATURE_ONE = "AA"
private const val DIGEST_ONE =
"BCEEF655B5A034911F1C3718CE056531B45EF03B4C7B1F15629E867294011A7D"
private const val SIGNATURE_TWO = "BB"
private val DOMAIN_BASE = DomainVerificationPackageTest::class.java.packageName
private val DOMAIN_1 = "one.$DOMAIN_BASE"
@@ -64,8 +71,8 @@ class DomainVerificationPackageTest {
private const val USER_ID = 0
}
private val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE)
private val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO)
private val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE, SIGNATURE_ONE)
private val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO, SIGNATURE_TWO)
@Test
fun addPackageFirstTime() {
@@ -93,6 +100,104 @@ class DomainVerificationPackageTest {
.containsExactly(pkg1.getName())
}
@Test
fun addPackageRestoredMatchingSignature() {
// language=XML
val xml = """
<?xml?>
<domain-verifications>
<active>
<package-state
packageName="${pkg1.getName()}"
id="${pkg1.domainSetId}"
signature="$DIGEST_ONE"
>
<state>
<domain name="$DOMAIN_1" state="1"/>
</state>
</package-state>
</active>
</domain-verifications>
"""
val service = makeService(pkg1, pkg2)
service.restoreSettings(Xml.resolvePullParser(xml.byteInputStream()))
service.addPackage(pkg1)
val info = service.getInfo(pkg1.getName())
assertThat(info.packageName).isEqualTo(pkg1.getName())
assertThat(info.identifier).isEqualTo(pkg1.domainSetId)
assertThat(info.hostToStateMap).containsExactlyEntriesIn(
mapOf(
DOMAIN_1 to STATE_MODIFIABLE_VERIFIED,
DOMAIN_2 to STATE_NO_RESPONSE,
)
)
val userState = service.getUserState(pkg1.getName())
assertThat(userState.packageName).isEqualTo(pkg1.getName())
assertThat(userState.identifier).isEqualTo(pkg1.domainSetId)
assertThat(userState.isLinkHandlingAllowed).isEqualTo(true)
assertThat(userState.user.identifier).isEqualTo(USER_ID)
assertThat(userState.hostToStateMap).containsExactlyEntriesIn(
mapOf(
DOMAIN_1 to DOMAIN_STATE_VERIFIED,
DOMAIN_2 to DOMAIN_STATE_NONE,
)
)
assertThat(service.queryValidVerificationPackageNames())
.containsExactly(pkg1.getName())
}
@Test
fun addPackageRestoredMismatchSignature() {
// language=XML
val xml = """
<?xml?>
<domain-verifications>
<active>
<package-state
packageName="${pkg1.getName()}"
id="${pkg1.domainSetId}"
signature="INVALID_SIGNATURE"
>
<state>
<domain name="$DOMAIN_1" state="1"/>
</state>
</package-state>
</active>
</domain-verifications>
"""
val service = makeService(pkg1, pkg2)
service.restoreSettings(Xml.resolvePullParser(xml.byteInputStream()))
service.addPackage(pkg1)
val info = service.getInfo(pkg1.getName())
assertThat(info.packageName).isEqualTo(pkg1.getName())
assertThat(info.identifier).isEqualTo(pkg1.domainSetId)
assertThat(info.hostToStateMap).containsExactlyEntriesIn(
mapOf(
DOMAIN_1 to STATE_NO_RESPONSE,
DOMAIN_2 to STATE_NO_RESPONSE,
)
)
val userState = service.getUserState(pkg1.getName())
assertThat(userState.packageName).isEqualTo(pkg1.getName())
assertThat(userState.identifier).isEqualTo(pkg1.domainSetId)
assertThat(userState.isLinkHandlingAllowed).isEqualTo(true)
assertThat(userState.user.identifier).isEqualTo(USER_ID)
assertThat(userState.hostToStateMap).containsExactlyEntriesIn(
mapOf(
DOMAIN_1 to DOMAIN_STATE_NONE,
DOMAIN_2 to DOMAIN_STATE_NONE,
)
)
assertThat(service.queryValidVerificationPackageNames())
.containsExactly(pkg1.getName())
}
@Test
fun addPackageActive() {
// language=XML
@@ -151,10 +256,9 @@ class DomainVerificationPackageTest {
@Test
fun migratePackageDropDomain() {
val pkgName = PKG_ONE
val pkgBefore = mockPkgSetting(pkgName, UUID_ONE,
listOf(DOMAIN_1, DOMAIN_2, DOMAIN_3, DOMAIN_4))
val pkgAfter = mockPkgSetting(pkgName, UUID_TWO,
listOf(DOMAIN_1, DOMAIN_2))
val pkgBefore = mockPkgSetting(pkgName, UUID_ONE, SIGNATURE_ONE,
listOf(DOMAIN_1, DOMAIN_2, DOMAIN_3, DOMAIN_4))
val pkgAfter = mockPkgSetting(pkgName, UUID_TWO, SIGNATURE_TWO, listOf(DOMAIN_1, DOMAIN_2))
// Test 4 domains:
// 1 will be approved and preserved, 2 will be selected and preserved,
@@ -213,8 +317,8 @@ class DomainVerificationPackageTest {
@Test
fun migratePackageDropAll() {
val pkgName = PKG_ONE
val pkgBefore = mockPkgSetting(pkgName, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2))
val pkgAfter = mockPkgSetting(pkgName, UUID_TWO, emptyList())
val pkgBefore = mockPkgSetting(pkgName, UUID_ONE, SIGNATURE_ONE, listOf(DOMAIN_1, DOMAIN_2))
val pkgAfter = mockPkgSetting(pkgName, UUID_TWO, SIGNATURE_TWO, emptyList())
val map = mutableMapOf<String, PackageSetting>()
val service = makeService { map[it] }
@@ -255,10 +359,9 @@ class DomainVerificationPackageTest {
@Test
fun migratePackageAddDomain() {
val pkgName = PKG_ONE
val pkgBefore = mockPkgSetting(pkgName, UUID_ONE,
listOf(DOMAIN_1, DOMAIN_2))
val pkgAfter = mockPkgSetting(pkgName, UUID_TWO,
listOf(DOMAIN_1, DOMAIN_2, DOMAIN_3))
val pkgBefore = mockPkgSetting(pkgName, UUID_ONE, SIGNATURE_ONE, listOf(DOMAIN_1, DOMAIN_2))
val pkgAfter = mockPkgSetting(pkgName, UUID_TWO, SIGNATURE_TWO,
listOf(DOMAIN_1, DOMAIN_2, DOMAIN_3))
// Test 3 domains:
// 1 will be verified and preserved, 2 will be selected and preserved,
@@ -307,8 +410,8 @@ class DomainVerificationPackageTest {
@Test
fun migratePackageAddAll() {
val pkgName = PKG_ONE
val pkgBefore = mockPkgSetting(pkgName, UUID_ONE, emptyList())
val pkgAfter = mockPkgSetting(pkgName, UUID_TWO, listOf(DOMAIN_1, DOMAIN_2))
val pkgBefore = mockPkgSetting(pkgName, UUID_ONE, SIGNATURE_ONE, emptyList())
val pkgAfter = mockPkgSetting(pkgName, UUID_TWO, SIGNATURE_TWO, listOf(DOMAIN_1, DOMAIN_2))
val map = mutableMapOf<String, PackageSetting>()
val service = makeService { map[it] }
@@ -379,21 +482,22 @@ class DomainVerificationPackageTest {
whenever(callingUid) { Process.ROOT_UID }
whenever(callingUserId) { 0 }
whenever(getPackageSettingLocked(anyString())) {
pkgSettingFunction(arguments[0] as String)!!
}
whenever(getPackageLocked(anyString())) {
pkgSettingFunction(arguments[0] as String)!!.getPkg()
mockPackageSettings {
pkgSettingFunction(it)
}
})
}
private fun mockPkgSetting(pkgName: String, domainSetId: UUID, domains: List<String> = listOf(
DOMAIN_1, DOMAIN_2
)) = mockThrowOnUnmocked<PackageSetting> {
private fun mockPkgSetting(
pkgName: String,
domainSetId: UUID,
signature: String,
domains: List<String> = listOf(DOMAIN_1, DOMAIN_2)
) = mockThrowOnUnmocked<PackageSetting> {
val pkg = mockThrowOnUnmocked<AndroidPackage> {
whenever(packageName) { pkgName }
whenever(targetSdkVersion) { Build.VERSION_CODES.S }
whenever(isEnabled) { true }
val activityList = listOf(
ParsedActivity().apply {
@@ -422,5 +526,7 @@ class DomainVerificationPackageTest {
whenever(this.domainSetId) { domainSetId }
whenever(getInstantApp(anyInt())) { false }
whenever(firstInstallTime) { 0L }
whenever(readUserState(USER_ID)) { PackageUserState() }
whenever(signatures) { arrayOf(Signature(signature)) }
}
}

View File

@@ -18,6 +18,7 @@ package com.android.server.pm.test.verify.domain
import android.content.pm.verify.domain.DomainVerificationState
import android.util.ArrayMap
import android.util.SparseArray
import android.util.TypedXmlPullParser
import android.util.TypedXmlSerializer
import android.util.Xml
@@ -84,23 +85,32 @@ class DomainVerificationPersistenceTest {
@JvmField
val tempFolder = TemporaryFolder()
@Test
fun writeAndReadBackNormal() {
private fun mockWriteValues(
pkgNameToSignature: (String) -> String? = { null }
): Triple<DomainVerificationStateMap<DomainVerificationPkgState>,
ArrayMap<String, DomainVerificationPkgState>,
ArrayMap<String, DomainVerificationPkgState>> {
val attached = DomainVerificationStateMap<DomainVerificationPkgState>().apply {
mockPkgState(0).let { put(it.packageName, it.id, it) }
mockPkgState(1).let { put(it.packageName, it.id, it) }
mockPkgState(0, pkgNameToSignature).let { put(it.packageName, it.id, it) }
mockPkgState(1, pkgNameToSignature).let { put(it.packageName, it.id, it) }
}
val pending = ArrayMap<String, DomainVerificationPkgState>().apply {
mockPkgState(2).let { put(it.packageName, it) }
mockPkgState(3).let { put(it.packageName, it) }
mockPkgState(2, pkgNameToSignature).let { put(it.packageName, it) }
mockPkgState(3, pkgNameToSignature).let { put(it.packageName, it) }
}
val restored = ArrayMap<String, DomainVerificationPkgState>().apply {
mockPkgState(4).let { put(it.packageName, it) }
mockPkgState(5).let { put(it.packageName, it) }
mockPkgState(4, pkgNameToSignature).let { put(it.packageName, it) }
mockPkgState(5, pkgNameToSignature).let { put(it.packageName, it) }
}
return Triple(attached, pending, restored)
}
@Test
fun writeAndReadBackNormal() {
val (attached, pending, restored) = mockWriteValues()
val file = tempFolder.newFile().writeXml {
DomainVerificationPersistence.writeToXml(it, attached, pending, restored)
DomainVerificationPersistence.writeToXml(it, attached, pending, restored, null)
}
val xml = file.readText()
@@ -114,9 +124,57 @@ class DomainVerificationPersistenceTest {
assertWithMessage(xml).that(readRestored.values).containsExactlyElementsIn(restored.values)
}
@Test
fun writeAndReadBackWithSignature() {
val (attached, pending, restored) = mockWriteValues()
val file = tempFolder.newFile().writeXml {
DomainVerificationPersistence.writeToXml(it, attached, pending, restored) {
"SIGNATURE_$it"
}
}
val (readActive, readRestored) = file.readXml {
DomainVerificationPersistence.readFromXml(it)
}
// Assign the signatures to a fresh set of data structures, to ensure the previous write
// call did not use the signatures from the data structure. This is because the method is
// intended to optionally append signatures, regardless of if the existing data structures
// contain them or not.
val (attached2, pending2, restored2) = mockWriteValues { "SIGNATURE_$it" }
assertThat(readActive.values)
.containsExactlyElementsIn(attached2.values() + pending2.values)
assertThat(readRestored.values).containsExactlyElementsIn(restored2.values)
(readActive + readRestored).forEach { (_, value) ->
assertThat(value.backupSignatureHash).isEqualTo("SIGNATURE_${value.packageName}")
}
}
@Test
fun writeStateSignatureIfFunctionReturnsNull() {
val (attached, pending, restored) = mockWriteValues { "SIGNATURE_$it" }
val file = tempFolder.newFile().writeXml {
DomainVerificationPersistence.writeToXml(it, attached, pending, restored) { null }
}
val (readActive, readRestored) = file.readXml {
DomainVerificationPersistence.readFromXml(it)
}
assertThat(readActive.values)
.containsExactlyElementsIn(attached.values() + pending.values)
assertThat(readRestored.values).containsExactlyElementsIn(restored.values)
(readActive + readRestored).forEach { (_, value) ->
assertThat(value.backupSignatureHash).isEqualTo("SIGNATURE_${value.packageName}")
}
}
@Test
fun readMalformed() {
val stateZero = mockEmptyPkgState(0).apply {
val stateZero = mockEmptyPkgState(0, pkgNameToSignature = { "ACTIVE" }).apply {
stateMap["example.com"] = DomainVerificationState.STATE_SUCCESS
stateMap["example.org"] = DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED
@@ -128,7 +186,7 @@ class DomainVerificationPersistenceTest {
isLinkHandlingAllowed = true
}
}
val stateOne = mockEmptyPkgState(1).apply {
val stateOne = mockEmptyPkgState(1, pkgNameToSignature = { "RESTORED" }).apply {
// It's valid to have a user selection without any autoVerify domains
userStates[1] = DomainVerificationInternalUserState(1).apply {
addHosts(setOf("example-user1.com", "example-user1.org"))
@@ -156,6 +214,7 @@ class DomainVerificationPersistenceTest {
packageName="${stateZero.packageName}"
id="${stateZero.id}"
hasAutoVerifyDomains="true"
signature="ACTIVE"
>
<state>
<domain name="example.com" state="${
@@ -191,6 +250,7 @@ class DomainVerificationPersistenceTest {
packageName="${stateOne.packageName}"
id="${stateOne.id}"
hasAutoVerifyDomains="true"
signature="RESTORED"
>
<state/>
<user-states>
@@ -226,20 +286,30 @@ class DomainVerificationPersistenceTest {
private fun mockEmptyPkgState(
id: Int,
hasAutoVerifyDomains: Boolean = true
hasAutoVerifyDomains: Boolean = true,
pkgNameToSignature: (String) -> String? = { null }
): DomainVerificationPkgState {
val pkgName = pkgName(id)
val domainSetId = UUID(0L, id.toLong())
return DomainVerificationPkgState(pkgName, domainSetId, hasAutoVerifyDomains)
return DomainVerificationPkgState(
pkgName,
domainSetId,
hasAutoVerifyDomains,
ArrayMap(),
SparseArray(),
pkgNameToSignature(pkgName)
)
}
private fun mockPkgState(id: Int) = mockEmptyPkgState(id).apply {
stateMap["$packageName.com"] = id
userStates[id] = DomainVerificationInternalUserState(id).apply {
addHosts(setOf("$packageName-user.com"))
isLinkHandlingAllowed = true
}
}
private fun mockPkgState(id: Int, pkgNameToSignature: (String) -> String? = { null }) =
mockEmptyPkgState(id, pkgNameToSignature = pkgNameToSignature)
.apply {
stateMap["$packageName.com"] = id
userStates[id] = DomainVerificationInternalUserState(id).apply {
addHosts(setOf("$packageName-user.com"))
isLinkHandlingAllowed = true
}
}
private fun pkgName(id: Int) = "$PKG_PREFIX.pkg$id"
}

View File

@@ -29,6 +29,7 @@ import android.util.ArraySet
import android.util.SparseArray
import com.android.server.pm.PackageSetting
import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.pm.test.verify.domain.DomainVerificationTestUtils.mockPackageSettings
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
import com.android.server.pm.verify.domain.DomainVerificationService
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy
@@ -192,6 +193,7 @@ class DomainVerificationSettingsMutationTest {
fun mockPkg() = mockThrowOnUnmocked<AndroidPackage> {
whenever(packageName) { TEST_PKG }
whenever(targetSdkVersion) { Build.VERSION_CODES.S }
whenever(isEnabled) { true }
whenever(activities) {
listOf(
ParsedActivity().apply {
@@ -233,11 +235,8 @@ class DomainVerificationSettingsMutationTest {
whenever(getName()) { TEST_PKG }
whenever(getPkg()) { mockPkg() }
whenever(domainSetId) { TEST_UUID }
whenever(userState) {
SparseArray<PackageUserState>().apply {
this[0] = PackageUserState()
}
}
whenever(readUserState(0)) { PackageUserState() }
whenever(readUserState(10)) { PackageUserState() }
whenever(getInstantApp(anyInt())) { false }
}
}
@@ -258,8 +257,12 @@ class DomainVerificationSettingsMutationTest {
mockThrowOnUnmocked {
whenever(callingUid) { TEST_UID }
whenever(callingUserId) { TEST_USER_ID }
whenever(getPackageSettingLocked(TEST_PKG)) { mockPkgSetting() }
whenever(getPackageLocked(TEST_PKG)) { mockPkg() }
mockPackageSettings {
when (it) {
TEST_PKG -> mockPkgSetting()
else -> null
}
}
whenever(schedule(anyInt(), any()))
whenever(scheduleWriteSettings())

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.pm.test.verify.domain
import com.android.internal.util.FunctionalUtils
import com.android.server.pm.PackageSetting
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
import com.android.server.testutils.whenever
import org.mockito.ArgumentMatchers.any
import java.util.function.Consumer
import java.util.function.Function
internal object DomainVerificationTestUtils {
@Suppress("UNCHECKED_CAST")
fun DomainVerificationManagerInternal.Connection.mockPackageSettings(
block: (String) -> PackageSetting?
) {
whenever(withPackageSettings(any())) {
(arguments[0] as Consumer<Function<String, PackageSetting?>>).accept { block(it) }
}
whenever(withPackageSettingsReturning<Any>(any())) {
(arguments[0] as FunctionalUtils.ThrowingFunction<Function<String, PackageSetting?>, *>)
.apply { block(it) }
}
whenever(withPackageSettingsThrowing<Exception>(any())) {
(arguments[0] as DomainVerificationManagerInternal.Connection.ThrowingConsumer<
Function<String, PackageSetting?>, *>)
.accept { block(it) }
}
whenever(withPackageSettingsReturningThrowing<Any, Exception>(any())) {
(arguments[0] as DomainVerificationManagerInternal.Connection.ThrowingFunction<
Function<String, PackageSetting?>, *, *>)
.apply { block(it) }
}
}
}

View File

@@ -18,6 +18,7 @@ package com.android.server.pm.test.verify.domain
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.PackageUserState
import android.content.pm.parsing.component.ParsedActivity
import android.content.pm.parsing.component.ParsedIntentInfo
import android.content.pm.verify.domain.DomainVerificationManager
@@ -29,6 +30,7 @@ import android.os.Process
import android.util.ArraySet
import com.android.server.pm.PackageSetting
import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.pm.test.verify.domain.DomainVerificationTestUtils.mockPackageSettings
import com.android.server.pm.verify.domain.DomainVerificationService
import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.whenever
@@ -82,10 +84,13 @@ class DomainVerificationUserStateOverrideTest {
// Need to provide an internal UID so some permission checks are ignored
whenever(callingUid) { Process.ROOT_UID }
whenever(callingUserId) { 0 }
whenever(getPackageSettingLocked(PKG_ONE)) { pkg1 }
whenever(getPackageSettingLocked(PKG_TWO)) { pkg2 }
whenever(getPackageLocked(PKG_ONE)) { pkg1.getPkg() }
whenever(getPackageLocked(PKG_TWO)) { pkg2.getPkg() }
mockPackageSettings {
when (it) {
PKG_ONE -> pkg1
PKG_TWO -> pkg2
else -> null
}
}
})
addPackage(pkg1)
addPackage(pkg2)
@@ -100,6 +105,7 @@ class DomainVerificationUserStateOverrideTest {
val pkg = mockThrowOnUnmocked<AndroidPackage> {
whenever(packageName) { pkgName }
whenever(targetSdkVersion) { Build.VERSION_CODES.S }
whenever(isEnabled) { true }
val activityList = listOf(
ParsedActivity().apply {
@@ -137,6 +143,8 @@ class DomainVerificationUserStateOverrideTest {
whenever(this.domainSetId) { domainSetId }
whenever(getInstantApp(anyInt())) { false }
whenever(firstInstallTime) { 0L }
whenever(readUserState(0)) { PackageUserState() }
whenever(readUserState(1)) { PackageUserState() }
}
@Test