Enforce isSystem for app-link SysConfig

Only apps that are preloaded on the system are allowed to be auto
approved. This prevents package name takeover for optional packages
that are still configured in the XML.

Bug: 166697328

Test: atest DomainVerificationPackageTest
Test: manual verify pm get-app-links on a device

Change-Id: Ic055db3c07ff876600402c3ed0fe66b30cebe09e
This commit is contained in:
Winson
2021-04-13 17:14:51 -07:00
parent 05f4f582b8
commit 762658fad1
6 changed files with 120 additions and 45 deletions

View File

@@ -892,7 +892,7 @@ public class DomainVerificationService extends SystemService
boolean hasAutoVerifyDomains = newDomainsSize > 0;
boolean needsBroadcast =
applyImmutableState(pkgName, newStateMap, newAutoVerifyDomains);
applyImmutableState(newPkgSetting, newStateMap, newAutoVerifyDomains);
sendBroadcast = hasAutoVerifyDomains && needsBroadcast;
@@ -943,7 +943,8 @@ public class DomainVerificationService extends SystemService
pkgState = new DomainVerificationPkgState(pkgName, domainSetId, hasAutoVerifyDomains);
}
boolean needsBroadcast = applyImmutableState(pkgState, domains);
boolean needsBroadcast =
applyImmutableState(newPkgSetting, pkgState.getStateMap(), domains);
if (needsBroadcast && !isPendingOrRestored) {
// TODO(b/159952358): Test this behavior
// Attempt to preserve user experience by automatically verifying all domains from
@@ -990,22 +991,17 @@ public class DomainVerificationService extends SystemService
}
}
private boolean applyImmutableState(@NonNull DomainVerificationPkgState pkgState,
@NonNull ArraySet<String> autoVerifyDomains) {
return applyImmutableState(pkgState.getPackageName(), pkgState.getStateMap(),
autoVerifyDomains);
}
/**
* Applies any immutable state as the final step when adding or migrating state. Currently only
* applies {@link SystemConfig#getLinkedApps()}, which approves all domains for a package.
* applies {@link SystemConfig#getLinkedApps()}, which approves all domains for a system app.
*
* @return whether or not a broadcast is necessary for this package
*/
private boolean applyImmutableState(@NonNull String packageName,
private boolean applyImmutableState(@NonNull PackageSetting pkgSetting,
@NonNull ArrayMap<String, Integer> stateMap,
@NonNull ArraySet<String> autoVerifyDomains) {
if (mSystemConfig.getLinkedApps().contains(packageName)) {
if (pkgSetting.isSystem()
&& mSystemConfig.getLinkedApps().contains(pkgSetting.getName())) {
int domainsSize = autoVerifyDomains.size();
for (int index = 0; index < domainsSize; index++) {
stateMap.put(autoVerifyDomains.valueAt(index),
@@ -1318,7 +1314,7 @@ public class DomainVerificationService extends SystemService
if (pkgSetting == null || pkgSetting.getPkg() == null) {
continue;
}
resetDomainState(pkgState, pkgSetting.getPkg());
resetDomainState(pkgState.getStateMap(), pkgSetting);
}
} else {
int size = packageNames.size();
@@ -1329,7 +1325,7 @@ public class DomainVerificationService extends SystemService
if (pkgSetting == null || pkgSetting.getPkg() == null) {
continue;
}
resetDomainState(pkgState, pkgSetting.getPkg());
resetDomainState(pkgState.getStateMap(), pkgSetting);
}
}
}
@@ -1341,9 +1337,8 @@ public class DomainVerificationService extends SystemService
/**
* Reset states that are mutable by the domain verification agent.
*/
private void resetDomainState(@NonNull DomainVerificationPkgState pkgState,
@NonNull AndroidPackage pkg) {
ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
private void resetDomainState(@NonNull ArrayMap<String, Integer> stateMap,
@NonNull PackageSetting pkgSetting) {
int size = stateMap.size();
for (int index = size - 1; index >= 0; index--) {
Integer state = stateMap.valueAt(index);
@@ -1363,7 +1358,8 @@ public class DomainVerificationService extends SystemService
}
}
applyImmutableState(pkgState, mCollector.collectValidAutoVerifyDomains(pkg));
applyImmutableState(pkgSetting, stateMap,
mCollector.collectValidAutoVerifyDomains(pkgSetting.getPkg()));
}
@Override

View File

@@ -338,6 +338,7 @@ class DomainVerificationEnforcerTest {
whenever(readUserState(0)) { PackageUserState() }
whenever(readUserState(1)) { PackageUserState() }
whenever(getInstantApp(anyInt())) { false }
whenever(isSystem()) { false }
}
}

View File

@@ -527,6 +527,7 @@ class DomainVerificationManagerApiTest {
whenever(firstInstallTime) { 0L }
whenever(readUserState(0)) { pkgUserState0() }
whenever(readUserState(1)) { pkgUserState1() }
whenever(isSystem()) { false }
}
private fun DomainVerificationService.addPackages(vararg pkgSettings: PackageSetting) =

View File

@@ -100,6 +100,70 @@ class DomainVerificationPackageTest {
.containsExactly(pkg1.getName())
}
@Test
fun addPackageSystemConfigured() {
val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE, SIGNATURE_ONE, isSystemApp = false)
val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO, SIGNATURE_TWO, isSystemApp = true)
val service = makeService(
systemConfiguredPackageNames = ArraySet(setOf(pkg1.getName(), pkg2.getName())),
pkg1, pkg2
)
service.addPackage(pkg1)
service.addPackage(pkg2)
service.getInfo(pkg1.getName()).apply {
assertThat(packageName).isEqualTo(pkg1.getName())
assertThat(identifier).isEqualTo(pkg1.domainSetId)
assertThat(hostToStateMap).containsExactlyEntriesIn(
mapOf(
DOMAIN_1 to STATE_NO_RESPONSE,
DOMAIN_2 to STATE_NO_RESPONSE,
)
)
}
service.getUserState(pkg1.getName()).apply {
assertThat(packageName).isEqualTo(pkg1.getName())
assertThat(identifier).isEqualTo(pkg1.domainSetId)
assertThat(isLinkHandlingAllowed).isEqualTo(true)
assertThat(user.identifier).isEqualTo(USER_ID)
assertThat(hostToStateMap).containsExactlyEntriesIn(
mapOf(
DOMAIN_1 to DOMAIN_STATE_NONE,
DOMAIN_2 to DOMAIN_STATE_NONE,
)
)
}
service.getInfo(pkg2.getName()).apply {
assertThat(packageName).isEqualTo(pkg2.getName())
assertThat(identifier).isEqualTo(pkg2.domainSetId)
assertThat(hostToStateMap).containsExactlyEntriesIn(
mapOf(
DOMAIN_1 to STATE_UNMODIFIABLE,
DOMAIN_2 to STATE_UNMODIFIABLE,
)
)
}
service.getUserState(pkg2.getName()).apply {
assertThat(packageName).isEqualTo(pkg2.getName())
assertThat(identifier).isEqualTo(pkg2.domainSetId)
assertThat(isLinkHandlingAllowed).isEqualTo(true)
assertThat(user.identifier).isEqualTo(USER_ID)
assertThat(hostToStateMap).containsExactlyEntriesIn(
mapOf(
DOMAIN_1 to DOMAIN_STATE_VERIFIED,
DOMAIN_2 to DOMAIN_STATE_VERIFIED,
)
)
}
assertThat(service.queryValidVerificationPackageNames())
.containsExactly(pkg1.getName(), pkg2.getName())
}
@Test
fun addPackageRestoredMatchingSignature() {
// language=XML
@@ -457,42 +521,52 @@ class DomainVerificationPackageTest {
getDomainVerificationUserState(pkgName, USER_ID)
.also { assertThat(it).isNotNull() }!!
private fun makeService(
systemConfiguredPackageNames: ArraySet<String> = ArraySet(),
vararg pkgSettings: PackageSetting
) = makeService(systemConfiguredPackageNames = systemConfiguredPackageNames) {
pkgName -> pkgSettings.find { pkgName == it.getName() }
}
private fun makeService(vararg pkgSettings: PackageSetting) =
makeService { pkgName -> pkgSettings.find { pkgName == it.getName()} }
makeService { pkgName -> pkgSettings.find { pkgName == it.getName() } }
private fun makeService(pkgSettingFunction: (String) -> PackageSetting? = { null }) =
DomainVerificationService(mockThrowOnUnmocked {
// Assume the test has every permission necessary
whenever(enforcePermission(anyString(), anyInt(), anyInt(), anyString()))
whenever(checkPermission(anyString(), anyInt(), anyInt())) {
PackageManager.PERMISSION_GRANTED
}
}, mockThrowOnUnmocked {
whenever(linkedApps) { ArraySet<String>() }
}, mockThrowOnUnmocked {
whenever(isChangeEnabledInternalNoLogging(anyLong(), any())) { true }
}).apply {
setConnection(mockThrowOnUnmocked {
whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false }
whenever(doesUserExist(0)) { true }
whenever(doesUserExist(1)) { true }
whenever(scheduleWriteSettings())
// Need to provide an internal UID so some permission checks are ignored
whenever(callingUid) { Process.ROOT_UID }
whenever(callingUserId) { 0 }
mockPackageSettings {
pkgSettingFunction(it)
}
})
private fun makeService(
systemConfiguredPackageNames: ArraySet<String> = ArraySet(),
pkgSettingFunction: (String) -> PackageSetting? = { null }
) = DomainVerificationService(mockThrowOnUnmocked {
// Assume the test has every permission necessary
whenever(enforcePermission(anyString(), anyInt(), anyInt(), anyString()))
whenever(checkPermission(anyString(), anyInt(), anyInt())) {
PackageManager.PERMISSION_GRANTED
}
}, mockThrowOnUnmocked {
whenever(this.linkedApps) { systemConfiguredPackageNames }
}, mockThrowOnUnmocked {
whenever(isChangeEnabledInternalNoLogging(anyLong(), any())) { true }
}).apply {
setConnection(mockThrowOnUnmocked {
whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false }
whenever(doesUserExist(0)) { true }
whenever(doesUserExist(1)) { true }
whenever(scheduleWriteSettings())
// Need to provide an internal UID so some permission checks are ignored
whenever(callingUid) { Process.ROOT_UID }
whenever(callingUserId) { 0 }
mockPackageSettings {
pkgSettingFunction(it)
}
})
}
private fun mockPkgSetting(
pkgName: String,
domainSetId: UUID,
signature: String,
domains: List<String> = listOf(DOMAIN_1, DOMAIN_2)
domains: List<String> = listOf(DOMAIN_1, DOMAIN_2),
isSystemApp: Boolean = false
) = mockThrowOnUnmocked<PackageSetting> {
val pkg = mockThrowOnUnmocked<AndroidPackage> {
whenever(packageName) { pkgName }
@@ -528,5 +602,6 @@ class DomainVerificationPackageTest {
whenever(firstInstallTime) { 0L }
whenever(readUserState(USER_ID)) { PackageUserState() }
whenever(signatures) { arrayOf(Signature(signature)) }
whenever(isSystem) { isSystemApp }
}
}

View File

@@ -238,6 +238,7 @@ class DomainVerificationSettingsMutationTest {
whenever(readUserState(0)) { PackageUserState() }
whenever(readUserState(10)) { PackageUserState() }
whenever(getInstantApp(anyInt())) { false }
whenever(isSystem()) { false }
}
}

View File

@@ -145,6 +145,7 @@ class DomainVerificationUserStateOverrideTest {
whenever(firstInstallTime) { 0L }
whenever(readUserState(0)) { PackageUserState() }
whenever(readUserState(1)) { PackageUserState() }
whenever(isSystem()) { false }
}
@Test