Fix domain collection for wildcards

The provided pattern won't match hosts that start with *. that are
intended to catch all subdomains.

Also transforms wildcard domains to their non-wildcard subdomain as
some implementations of v1 don't handle wildcards.

Bug: 179686283

Test: android.os.cts.OsHostTests#testIntentFilterHostValidation

Change-Id: I61d2a20495b9394c25739285c4a480c06fba5f76
This commit is contained in:
Winson
2021-02-09 15:40:03 -08:00
parent 5487bde80f
commit 71c5debd66
2 changed files with 93 additions and 22 deletions

View File

@@ -21,11 +21,8 @@ import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.parsing.component.ParsedActivity;
import android.content.pm.parsing.component.ParsedIntentInfo;
import android.os.Binder;
import android.os.Build;
import android.util.ArraySet;
import android.util.Patterns;
@@ -36,19 +33,31 @@ import com.android.server.pm.parsing.pkg.AndroidPackage;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class DomainVerificationCollector {
// The default domain name matcher doesn't account for wildcards, so prefix with *.
private static final Pattern DOMAIN_NAME_WITH_WILDCARD =
Pattern.compile("(\\*\\.)?" + Patterns.DOMAIN_NAME.pattern());
@NonNull
private final PlatformCompat mPlatformCompat;
@NonNull
private final SystemConfig mSystemConfig;
@NonNull
private final Matcher mDomainMatcher;
public DomainVerificationCollector(@NonNull PlatformCompat platformCompat,
@NonNull SystemConfig systemConfig) {
mPlatformCompat = platformCompat;
mSystemConfig = systemConfig;
// Cache the matcher to avoid calling into native on each check
mDomainMatcher = DOMAIN_NAME_WITH_WILDCARD.matcher("");
}
/**
@@ -144,7 +153,10 @@ public class DomainVerificationCollector {
if (intent.handlesWebUris(false)) {
int authorityCount = intent.countDataAuthorities();
for (int index = 0; index < authorityCount; index++) {
domains.add(intent.getDataAuthority(index).getHost());
String host = intent.getDataAuthority(index).getHost();
if (isValidHost(host)) {
domains.add(host);
}
}
}
}
@@ -188,13 +200,22 @@ public class DomainVerificationCollector {
int authorityCount = intent.countDataAuthorities();
for (int index = 0; index < authorityCount; index++) {
String host = intent.getDataAuthority(index).getHost();
// It's easy to misconfigure autoVerify intent filters, so to avoid
// adding unintended hosts, check if the host is an HTTP domain.
if (Patterns.DOMAIN_NAME.matcher(host).matches()) {
if (isValidHost(host)) {
domains.add(host);
}
}
}
}
}
/**
* It's easy to mis-configure autoVerify intent filters, so to avoid adding unintended hosts,
* check if the host is an HTTP domain. This applies for both legacy and modern versions of
* the API, which will strip invalid hosts from the legacy parsing result. This is done to
* improve the reliability of any legacy verifiers.
*/
private boolean isValidHost(String host) {
mDomainMatcher.reset(host);
return mDomainMatcher.matches();
}
}

View File

@@ -30,7 +30,6 @@ import android.content.pm.verify.domain.DomainVerificationManager;
import android.content.pm.verify.domain.DomainVerificationState;
import android.os.Process;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Pair;
@@ -45,6 +44,7 @@ import com.android.server.pm.verify.domain.DomainVerificationMessageCodes;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
@@ -168,22 +168,58 @@ public class DomainVerificationProxyV1 implements DomainVerificationProxy {
return true;
}
Set<String> successfulDomains = new ArraySet<>(info.getHostToStateMap().keySet());
successfulDomains.removeAll(response.failedDomains);
AndroidPackage pkg = mConnection.getPackage(packageName);
if (pkg == null) {
return true;
}
ArraySet<String> failedDomains = new ArraySet<>(response.failedDomains);
Map<String, Integer> hostToStateMap = info.getHostToStateMap();
Set<String> hostKeySet = hostToStateMap.keySet();
ArraySet<String> successfulDomains = new ArraySet<>(hostKeySet);
successfulDomains.removeAll(failedDomains);
// v1 doesn't handle wildcard domains, so check them here for the verifier
int size = successfulDomains.size();
for (int index = size - 1; index >= 0; index--) {
String domain = successfulDomains.valueAt(index);
if (domain.startsWith("*.")) {
String nonWildcardDomain = domain.substring(2);
if (failedDomains.contains(nonWildcardDomain)) {
failedDomains.add(domain);
successfulDomains.removeAt(index);
// It's possible to declare a wildcard without declaring its
// non-wildcard equivalent, so if it wasn't originally declared,
// remove the transformed domain from the failed set. Otherwise the
// manager will not accept the failed set as it contains an undeclared
// domain.
if (!hostKeySet.contains(nonWildcardDomain)) {
failedDomains.remove(nonWildcardDomain);
}
}
}
}
int callingUid = response.callingUid;
try {
mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
successfulDomains, DomainVerificationState.STATE_SUCCESS);
} catch (DomainVerificationManager.InvalidDomainSetException
| PackageManager.NameNotFoundException ignored) {
if (!successfulDomains.isEmpty()) {
try {
mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
successfulDomains, DomainVerificationState.STATE_SUCCESS);
} catch (DomainVerificationManager.InvalidDomainSetException
| PackageManager.NameNotFoundException e) {
Slog.e(TAG, "Failure reporting successful domains for " + packageName, e);
}
}
try {
mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
new ArraySet<>(response.failedDomains),
DomainVerificationState.STATE_LEGACY_FAILURE);
} catch (DomainVerificationManager.InvalidDomainSetException
| PackageManager.NameNotFoundException ignored) {
if (!failedDomains.isEmpty()) {
try {
mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
failedDomains, DomainVerificationState.STATE_LEGACY_FAILURE);
} catch (DomainVerificationManager.InvalidDomainSetException
| PackageManager.NameNotFoundException e) {
Slog.e(TAG, "Failure reporting failed domains for " + packageName, e);
}
}
return true;
@@ -235,7 +271,21 @@ public class DomainVerificationProxyV1 implements DomainVerificationProxy {
// The collector itself handles the v1 vs v2 behavior, which is based on targetSdkVersion,
// not the version of the verification agent on device.
ArraySet<String> domains = mCollector.collectAutoVerifyDomains(pkg);
return TextUtils.join(" ", domains);
// v1 doesn't handle wildcard domains, so transform them here to the root
StringBuilder builder = new StringBuilder();
int size = domains.size();
for (int index = 0; index < size; index++) {
if (index > 0) {
builder.append(" ");
}
String domain = domains.valueAt(index);
if (domain.startsWith("*.")) {
domain = domain.substring(2);
}
builder.append(domain);
}
return builder.toString();
}
private static class Response {