Refactor legacy domain verification code

Moves everything to com.android.server.pm.intent.verify.legacy, in
preparation for replacement with new classes.

No functional changes were made, although the code may be slightly
slower since lambdas are now passed around to do locking.

Eventually the entire legacy package will be deleted. Any attempts at
backwards compatbility will involve a brand new wrapper of the v1 APIs
which delegate into the v2 methods.

Exempt-From-Owner-Approval: Already approved by owners on main branch

Bug: 163565078

Test: atest IntentFilterVerificationTest
Test: manual, verify with `dumpsys package d` that an app auto verifies

Change-Id: Id7d428b939cab6dd887567abcc7ba0e8f3fb7638
This commit is contained in:
Winson
2020-10-07 10:26:45 -07:00
parent debc58e074
commit 1e8c37a0dc
13 changed files with 1782 additions and 1015 deletions

View File

@@ -35,6 +35,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.IntFunction;
/**
@@ -599,6 +600,20 @@ public class ArrayUtils {
return cur;
}
/**
* Similar to {@link Set#addAll(Collection)}}, but with support for set values of {@code null}.
*/
public static @NonNull <T> ArraySet<T> addAll(@Nullable ArraySet<T> cur,
@Nullable Collection<T> val) {
if (cur == null) {
cur = new ArraySet<>();
}
if (val != null) {
cur.addAll(val);
}
return cur;
}
public static @Nullable <T> ArraySet<T> remove(@Nullable ArraySet<T> cur, T val) {
if (cur == null) {
return null;

View File

@@ -644,11 +644,11 @@ public abstract class PackageSettingBase extends SettingBase {
return excludedUserIds;
}
IntentFilterVerificationInfo getIntentFilterVerificationInfo() {
public IntentFilterVerificationInfo getIntentFilterVerificationInfo() {
return verificationInfo;
}
void setIntentFilterVerificationInfo(IntentFilterVerificationInfo info) {
public void setIntentFilterVerificationInfo(IntentFilterVerificationInfo info) {
verificationInfo = info;
onChanged();
}
@@ -657,14 +657,14 @@ public abstract class PackageSettingBase extends SettingBase {
//
// high 'int'-sized word: link status: undefined/ask/never/always.
// low 'int'-sized word: relative priority among 'always' results.
long getDomainVerificationStatusForUser(int userId) {
public long getDomainVerificationStatusForUser(int userId) {
PackageUserState state = readUserState(userId);
long result = (long) state.appLinkGeneration;
result |= ((long) state.domainVerificationStatus) << 32;
return result;
}
void setDomainVerificationStatusForUser(final int status, int generation, int userId) {
public void setDomainVerificationStatusForUser(final int status, int generation, int userId) {
PackageUserState state = modifyUserState(userId);
state.domainVerificationStatus = status;
if (status == PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) {
@@ -673,7 +673,7 @@ public abstract class PackageSettingBase extends SettingBase {
}
}
void clearDomainVerificationStatusForUser(int userId) {
public void clearDomainVerificationStatusForUser(int userId) {
modifyUserState(userId).domainVerificationStatus =
PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
}

View File

@@ -21,7 +21,6 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
import static android.content.pm.PackageManager.UNINSTALL_REASON_UNKNOWN;
@@ -29,7 +28,6 @@ import static android.content.pm.PackageManager.UNINSTALL_REASON_USER_TYPE;
import static android.os.Process.PACKAGE_INFO_GID;
import static android.os.Process.SYSTEM_UID;
import static com.android.server.pm.PackageManagerService.DEBUG_DOMAIN_VERIFICATION;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import android.annotation.NonNull;
@@ -108,6 +106,7 @@ import com.android.permission.persistence.RuntimePermissionsState;
import com.android.server.LocalServices;
import com.android.server.backup.PreferredActivityBackupHelper;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.intent.verify.legacy.IntentFilterVerificationManager;
import com.android.server.pm.parsing.PackageInfoUtils;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
@@ -131,6 +130,7 @@ import libcore.io.IoUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.BufferedWriter;
import java.io.File;
@@ -147,7 +147,6 @@ import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
@@ -283,9 +282,9 @@ public final class Settings implements Watchable, Snappable {
"persistent-preferred-activities";
static final String TAG_CROSS_PROFILE_INTENT_FILTERS =
"crossProfile-intent-filters";
private static final String TAG_DOMAIN_VERIFICATION = "domain-verification";
public static final String TAG_DOMAIN_VERIFICATION = "domain-verification";
private static final String TAG_DEFAULT_APPS = "default-apps";
private static final String TAG_ALL_INTENT_FILTER_VERIFICATION =
public static final String TAG_ALL_INTENT_FILTER_VERIFICATION =
"all-intent-filter-verifications";
private static final String TAG_DEFAULT_BROWSER = "default-browser";
private static final String TAG_DEFAULT_DIALER = "default-dialer";
@@ -390,12 +389,6 @@ public final class Settings implements Watchable, Snappable {
private final WatchedSparseArray<ArraySet<String>> mBlockUninstallPackages =
new WatchedSparseArray<>();
// Set of restored intent-filter verification states
@Watched
private final WatchedArrayMap<String, IntentFilterVerificationInfo>
mRestoredIntentFilterVerifications =
new WatchedArrayMap<String, IntentFilterVerificationInfo>();
private static final class KernelPackageState {
int appId;
int[] excludedUserIds;
@@ -487,7 +480,10 @@ public final class Settings implements Watchable, Snappable {
@Watched
final WatchedSparseArray<String> mDefaultBrowserApp = new WatchedSparseArray<String>();
// TODO(b/161161364): This seems unused, and is probably not relevant in the new API, but should
// verify.
// App-link priority tracking, per-user
@NonNull
@Watched
final WatchedSparseIntArray mNextAppLinkGeneration = new WatchedSparseIntArray();
@@ -512,6 +508,8 @@ public final class Settings implements Watchable, Snappable {
private final LegacyPermissionDataProvider mPermissionDataProvider;
private final IntentFilterVerificationManager mIntentFilterVerificationManager;
/**
* The observer that watches for changes from array members
*/
@@ -538,13 +536,12 @@ public final class Settings implements Watchable, Snappable {
mStoppedPackagesFilename = null;
mBackupStoppedPackagesFilename = null;
mKernelMappingFilename = null;
mIntentFilterVerificationManager = null;
mPackages.registerObserver(mObserver);
mInstallerPackages.registerObserver(mObserver);
mKernelMapping.registerObserver(mObserver);
mDisabledSysPackages.registerObserver(mObserver);
mBlockUninstallPackages.registerObserver(mObserver);
mRestoredIntentFilterVerifications.registerObserver(mObserver);
mVersion.registerObserver(mObserver);
mPreferredActivities.registerObserver(mObserver);
mPersistentPreferredActivities.registerObserver(mObserver);
@@ -560,7 +557,8 @@ public final class Settings implements Watchable, Snappable {
}
Settings(File dataDir, RuntimePermissionsPersistence runtimePermissionsPersistence,
LegacyPermissionDataProvider permissionDataProvider, Object lock) {
LegacyPermissionDataProvider permissionDataProvider,
IntentFilterVerificationManager intentFilterVerificationManager, Object lock) {
mLock = lock;
mAppIds = new WatchedArrayList<>();
mOtherAppIds = new WatchedSparseArray<>();
@@ -587,12 +585,13 @@ public final class Settings implements Watchable, Snappable {
mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml");
mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml");
mIntentFilterVerificationManager = intentFilterVerificationManager;
mPackages.registerObserver(mObserver);
mInstallerPackages.registerObserver(mObserver);
mKernelMapping.registerObserver(mObserver);
mDisabledSysPackages.registerObserver(mObserver);
mBlockUninstallPackages.registerObserver(mObserver);
mRestoredIntentFilterVerifications.registerObserver(mObserver);
mVersion.registerObserver(mObserver);
mPreferredActivities.registerObserver(mObserver);
mPersistentPreferredActivities.registerObserver(mObserver);
@@ -629,11 +628,12 @@ public final class Settings implements Watchable, Snappable {
mBackupStoppedPackagesFilename = null;
mKernelMappingFilename = null;
mIntentFilterVerificationManager = r.mIntentFilterVerificationManager;
mInstallerPackages.addAll(r.mInstallerPackages);
mKernelMapping.putAll(r.mKernelMapping);
mDisabledSysPackages.putAll(r.mDisabledSysPackages);
mBlockUninstallPackages.snapshot(r.mBlockUninstallPackages);
mRestoredIntentFilterVerifications.putAll(r.mRestoredIntentFilterVerifications);
mVersion.putAll(r.mVersion);
mVerifierDeviceIdentity = r.mVerifierDeviceIdentity;
WatchedSparseArray.snapshot(
@@ -1169,13 +1169,10 @@ public final class Settings implements Watchable, Snappable {
}
}
IntentFilterVerificationInfo ivi = mRestoredIntentFilterVerifications.get(p.name);
if (ivi != null) {
if (DEBUG_DOMAIN_VERIFICATION) {
Slog.i(TAG, "Applying restored IVI for " + p.name + " : " + ivi.getStatusString());
}
mRestoredIntentFilterVerifications.remove(p.name);
p.setIntentFilterVerificationInfo(ivi);
IntentFilterVerificationInfo info =
mIntentFilterVerificationManager.getRestoredIntentFilterVerificationInfo(p.name);
if (info != null) {
p.setIntentFilterVerificationInfo(info);
}
}
@@ -1307,129 +1304,6 @@ public final class Settings implements Watchable, Snappable {
return cpir;
}
/**
* The following functions suppose that you have a lock for managing access to the
* mIntentFiltersVerifications map.
*/
/* package protected */
IntentFilterVerificationInfo getIntentFilterVerificationLPr(String packageName) {
PackageSetting ps = mPackages.get(packageName);
if (ps == null) {
if (DEBUG_DOMAIN_VERIFICATION) {
Slog.w(PackageManagerService.TAG, "No package known: " + packageName);
}
return null;
}
return ps.getIntentFilterVerificationInfo();
}
/* package protected */
IntentFilterVerificationInfo createIntentFilterVerificationIfNeededLPw(String packageName,
ArraySet<String> domains) {
PackageSetting ps = mPackages.get(packageName);
if (ps == null) {
if (DEBUG_DOMAIN_VERIFICATION) {
Slog.w(PackageManagerService.TAG, "No package known: " + packageName);
}
return null;
}
IntentFilterVerificationInfo ivi = ps.getIntentFilterVerificationInfo();
if (ivi == null) {
ivi = new IntentFilterVerificationInfo(packageName, domains);
ps.setIntentFilterVerificationInfo(ivi);
if (DEBUG_DOMAIN_VERIFICATION) {
Slog.d(PackageManagerService.TAG,
"Creating new IntentFilterVerificationInfo for pkg: " + packageName);
}
} else {
ivi.setDomains(domains);
if (DEBUG_DOMAIN_VERIFICATION) {
Slog.d(PackageManagerService.TAG,
"Setting domains to existing IntentFilterVerificationInfo for pkg: " +
packageName + " and with domains: " + ivi.getDomainsString());
}
}
return ivi;
}
int getIntentFilterVerificationStatusLPr(String packageName, int userId) {
PackageSetting ps = mPackages.get(packageName);
if (ps == null) {
if (DEBUG_DOMAIN_VERIFICATION) {
Slog.w(PackageManagerService.TAG, "No package known: " + packageName);
}
return INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
}
return (int)(ps.getDomainVerificationStatusForUser(userId) >> 32);
}
boolean updateIntentFilterVerificationStatusLPw(String packageName, final int status, int userId) {
// Update the status for the current package
PackageSetting current = mPackages.get(packageName);
if (current == null) {
if (DEBUG_DOMAIN_VERIFICATION) {
Slog.w(PackageManagerService.TAG, "No package known: " + packageName);
}
return false;
}
final int alwaysGeneration;
if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) {
alwaysGeneration = mNextAppLinkGeneration.get(userId) + 1;
mNextAppLinkGeneration.put(userId, alwaysGeneration);
} else {
alwaysGeneration = 0;
}
current.setDomainVerificationStatusForUser(status, alwaysGeneration, userId);
return true;
}
/**
* Used for Settings App and PackageManagerService dump. Should be read only.
*/
List<IntentFilterVerificationInfo> getIntentFilterVerificationsLPr(
String packageName) {
if (packageName == null) {
return Collections.<IntentFilterVerificationInfo>emptyList();
}
ArrayList<IntentFilterVerificationInfo> result = new ArrayList<>();
for (PackageSetting ps : mPackages.values()) {
IntentFilterVerificationInfo ivi = ps.getIntentFilterVerificationInfo();
if (ivi == null || TextUtils.isEmpty(ivi.getPackageName()) ||
!ivi.getPackageName().equalsIgnoreCase(packageName)) {
continue;
}
result.add(ivi);
}
return result;
}
boolean removeIntentFilterVerificationLPw(String packageName, int userId,
boolean alsoResetStatus) {
PackageSetting ps = mPackages.get(packageName);
if (ps == null) {
if (DEBUG_DOMAIN_VERIFICATION) {
Slog.w(PackageManagerService.TAG, "No package known: " + packageName);
}
return false;
}
if (alsoResetStatus) {
ps.clearDomainVerificationStatusForUser(userId);
}
ps.setIntentFilterVerificationInfo(null);
return true;
}
boolean removeIntentFilterVerificationLPw(String packageName, int[] userIds) {
boolean result = false;
for (int userId : userIds) {
result |= removeIntentFilterVerificationLPw(packageName, userId, true);
}
return result;
}
String removeDefaultBrowserPackageNameLPw(int userId) {
return (userId == UserHandle.USER_ALL) ? null : mDefaultBrowserApp.removeReturnOld(userId);
}
@@ -1591,40 +1465,7 @@ public final class Settings implements Watchable, Snappable {
}
}
private void readDomainVerificationLPw(TypedXmlPullParser parser,
PackageSettingBase packageSetting) throws XmlPullParserException, IOException {
IntentFilterVerificationInfo ivi = new IntentFilterVerificationInfo(parser);
packageSetting.setIntentFilterVerificationInfo(ivi);
if (DEBUG_PARSER) {
Log.d(TAG, "Read domain verification for package: " + ivi.getPackageName());
}
}
private void readRestoredIntentFilterVerifications(TypedXmlPullParser parser)
throws XmlPullParserException, IOException {
int outerDepth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
final String tagName = parser.getName();
if (tagName.equals(TAG_DOMAIN_VERIFICATION)) {
IntentFilterVerificationInfo ivi = new IntentFilterVerificationInfo(parser);
if (DEBUG_DOMAIN_VERIFICATION) {
Slog.i(TAG, "Restored IVI for " + ivi.getPackageName()
+ " status=" + ivi.getStatusString());
}
mRestoredIntentFilterVerifications.put(ivi.getPackageName(), ivi);
} else {
Slog.w(TAG, "Unknown element: " + tagName);
XmlUtils.skipCurrentTag(parser);
}
}
}
void readDefaultAppsLPw(TypedXmlPullParser parser, int userId)
void readDefaultAppsLPw(XmlPullParser parser, int userId)
throws XmlPullParserException, IOException {
int outerDepth = parser.getDepth();
int type;
@@ -2038,77 +1879,7 @@ public final class Settings implements Watchable, Snappable {
serializer.endTag(null, TAG_CROSS_PROFILE_INTENT_FILTERS);
}
void writeDomainVerificationsLPr(TypedXmlSerializer serializer,
IntentFilterVerificationInfo verificationInfo)
throws IllegalArgumentException, IllegalStateException, IOException {
if (verificationInfo != null && verificationInfo.getPackageName() != null) {
serializer.startTag(null, TAG_DOMAIN_VERIFICATION);
verificationInfo.writeToXml(serializer);
if (DEBUG_DOMAIN_VERIFICATION) {
Slog.d(TAG, "Wrote domain verification for package: "
+ verificationInfo.getPackageName());
}
serializer.endTag(null, TAG_DOMAIN_VERIFICATION);
}
}
// Specifically for backup/restore
void writeAllDomainVerificationsLPr(TypedXmlSerializer serializer, int userId)
throws IllegalArgumentException, IllegalStateException, IOException {
serializer.startTag(null, TAG_ALL_INTENT_FILTER_VERIFICATION);
final int N = mPackages.size();
for (int i = 0; i < N; i++) {
PackageSetting ps = mPackages.valueAt(i);
IntentFilterVerificationInfo ivi = ps.getIntentFilterVerificationInfo();
if (ivi != null) {
writeDomainVerificationsLPr(serializer, ivi);
}
}
serializer.endTag(null, TAG_ALL_INTENT_FILTER_VERIFICATION);
}
// Specifically for backup/restore
void readAllDomainVerificationsLPr(TypedXmlPullParser parser, int userId)
throws XmlPullParserException, IOException {
mRestoredIntentFilterVerifications.clear();
int outerDepth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
String tagName = parser.getName();
if (tagName.equals(TAG_DOMAIN_VERIFICATION)) {
IntentFilterVerificationInfo ivi = new IntentFilterVerificationInfo(parser);
final String pkgName = ivi.getPackageName();
final PackageSetting ps = mPackages.get(pkgName);
if (ps != null) {
// known/existing package; update in place
ps.setIntentFilterVerificationInfo(ivi);
if (DEBUG_DOMAIN_VERIFICATION) {
Slog.d(TAG, "Restored IVI for existing app " + pkgName
+ " status=" + ivi.getStatusString());
}
} else {
mRestoredIntentFilterVerifications.put(pkgName, ivi);
if (DEBUG_DOMAIN_VERIFICATION) {
Slog.d(TAG, "Restored IVI for pending app " + pkgName
+ " status=" + ivi.getStatusString());
}
}
} else {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Unknown element under <all-intent-filter-verification>: "
+ parser.getName());
XmlUtils.skipCurrentTag(parser);
}
}
}
void writeDefaultAppsLPr(TypedXmlSerializer serializer, int userId)
void writeDefaultAppsLPr(XmlSerializer serializer, int userId)
throws IllegalArgumentException, IllegalStateException, IOException {
serializer.startTag(null, TAG_DEFAULT_APPS);
String defaultBrowser = mDefaultBrowserApp.get(userId);
@@ -2569,22 +2340,7 @@ public final class Settings implements Watchable, Snappable {
}
}
final int numIVIs = mRestoredIntentFilterVerifications.size();
if (numIVIs > 0) {
if (DEBUG_DOMAIN_VERIFICATION) {
Slog.i(TAG, "Writing restored-ivi entries to packages.xml");
}
serializer.startTag(null, "restored-ivi");
for (int i = 0; i < numIVIs; i++) {
IntentFilterVerificationInfo ivi = mRestoredIntentFilterVerifications.valueAt(i);
writeDomainVerificationsLPr(serializer, ivi);
}
serializer.endTag(null, "restored-ivi");
} else {
if (DEBUG_DOMAIN_VERIFICATION) {
Slog.i(TAG, " no restored IVI entries to write");
}
}
mIntentFilterVerificationManager.writeRestoredIntentFilterVerifications(serializer);
mKeySetManagerService.writeKeySetManagerServiceLPr(serializer);
@@ -2973,7 +2729,8 @@ public final class Settings implements Watchable, Snappable {
writeSigningKeySetLPr(serializer, pkg.keySetData);
writeUpgradeKeySetsLPr(serializer, pkg.keySetData);
writeKeySetAliasesLPr(serializer, pkg.keySetData);
writeDomainVerificationsLPr(serializer, pkg.verificationInfo);
mIntentFilterVerificationManager.writeDomainVerificationsLPr(serializer,
pkg.verificationInfo);
writeMimeGroupLPr(serializer, pkg.mimeGroups);
serializer.endTag(null, "package");
@@ -3105,7 +2862,7 @@ public final class Settings implements Watchable, Snappable {
mRenamedPackages.put(nname, oname);
}
} else if (tagName.equals("restored-ivi")) {
readRestoredIntentFilterVerifications(parser);
mIntentFilterVerificationManager.readRestoredIntentFilterVerifications(parser);
} else if (tagName.equals("last-platform-version")) {
// Upgrade from older XML schema
final VersionInfo internal = findOrCreateVersion(
@@ -3946,7 +3703,12 @@ public final class Settings implements Watchable, Snappable {
packageSetting.installSource =
packageSetting.installSource.setInitiatingPackageSignatures(signatures);
} else if (tagName.equals(TAG_DOMAIN_VERIFICATION)) {
readDomainVerificationLPw(parser, packageSetting);
IntentFilterVerificationInfo ivi =
mIntentFilterVerificationManager.readDomainVerificationLPw(parser);
packageSetting.setIntentFilterVerificationInfo(ivi);
if (DEBUG_PARSER) {
Log.d(TAG, "Read domain verification for package: " + ivi.getPackageName());
}
} else if (tagName.equals(TAG_MIME_GROUP)) {
packageSetting.mimeGroups = readMimeGroupLPw(parser, packageSetting.mimeGroups);
} else if (tagName.equals(TAG_USES_STATIC_LIB)) {

View File

@@ -0,0 +1,64 @@
/*
* Copyright (C) 2020 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.intent.verify.legacy;
/**
* This is the key for the map of {@link android.content.pm.IntentFilterVerificationInfo}s
* maintained by the {@link com.android.server.pm.PackageManagerService}
*/
class IntentFilterVerificationKey {
public String domains;
public String packageName;
public String className;
public IntentFilterVerificationKey(String[] domains, String packageName, String className) {
StringBuilder sb = new StringBuilder();
for (String host : domains) {
sb.append(host);
}
this.domains = sb.toString();
this.packageName = packageName;
this.className = className;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
IntentFilterVerificationKey that = (IntentFilterVerificationKey) o;
if (domains != null ? !domains.equals(that.domains) : that.domains != null) return false;
if (className != null ? !className.equals(that.className) : that.className != null) {
return false;
}
if (packageName != null ? !packageName.equals(that.packageName)
: that.packageName != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = domains != null ? domains.hashCode() : 0;
result = 31 * result + (packageName != null ? packageName.hashCode() : 0);
result = 31 * result + (className != null ? className.hashCode() : 0);
return result;
}
}

View File

@@ -0,0 +1,584 @@
/*
* Copyright (C) 2020 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.intent.verify.legacy;
import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.parsing.component.ParsedActivity;
import android.content.pm.parsing.component.ParsedIntentInfo;
import android.os.Binder;
import android.os.Handler;
import android.os.Message;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.SparseArray;
import android.util.StringBuilderPrinter;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.server.SystemConfig;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.PackageSetting;
import com.android.server.pm.UserManagerService;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.utils.WatchedArrayMap;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class IntentFilterVerificationManager {
private final Context mContext;
private final Handler mHandler;
private final IntentVerifierProxy.PackageManagerServiceConnection mConnection;
private final SystemConfig mSystemConfig;
private final IntentFilterVerificationSettings mSettings;
private final IntentVerifierProxy mVerifier;
private int mIntentFilterVerificationToken = 0;
private boolean mHasVerifier;
private final SparseArray<IntentFilterVerificationState> mStates = new SparseArray<>();
public IntentFilterVerificationManager(Context context, Handler handler,
IntentVerifierProxy.PackageManagerServiceConnection connection,
SystemConfig systemConfig, UserManagerService userManager) {
mContext = context;
mHandler = handler;
mConnection = connection;
mSystemConfig = systemConfig;
mSettings = new IntentFilterVerificationSettings(mContext, userManager, connection);
mVerifier = new IntentVerifierProxy(mContext, connection);
}
public void setVerifierComponent(@Nullable ComponentName componentName) {
mVerifier.setComponent(componentName);
mHasVerifier = componentName != null;
}
@Nullable
public ComponentName getVerifierComponent() {
return mVerifier.getComponent();
}
public void startIntentFilterVerifications(int userId, boolean replacing, AndroidPackage pkg) {
if (!mHasVerifier) {
mConnection.warnLog("No IntentFilter verification will not be done as "
+ "there is no IntentFilterVerifier available!");
return;
}
final int verifierUid = mConnection.getPackageUid(
mVerifier.getComponent().getPackageName(),
MATCH_DEBUG_TRIAGED_MISSING,
(userId == UserHandle.USER_ALL) ? UserHandle.USER_SYSTEM : userId);
Message msg = mHandler.obtainMessage(
PackageManagerService.START_INTENT_FILTER_VERIFICATIONS);
msg.obj = new IntentFilterVerificationParams(
pkg.getPackageName(),
pkg.isHasDomainUrls(),
pkg.getActivities(),
replacing,
userId,
verifierUid
);
mHandler.sendMessage(msg);
}
public void verifyIntentFiltersIfNeeded(IntentFilterVerificationParams params) {
if (!mHasVerifier) {
return;
}
int userId = params.userId;
int verifierUid = params.verifierUid;
boolean replacing = params.replacing;
String packageName = params.packageName;
boolean hasDomainUrls = params.hasDomainUrls;
List<ParsedActivity> activities = params.activities;
int size = activities.size();
if (size == 0) {
mConnection.debugLog("No activity, so no need to verify any IntentFilter!");
return;
}
if (!hasDomainUrls) {
mConnection.debugLog("No domain URLs, so no need to verify any IntentFilter!");
return;
}
mConnection.debugLog("Checking for userId:" + userId
+ " if any IntentFilter from the " + size
+ " Activities needs verification ...");
boolean runVerify = mConnection.lockReturn(() -> {
int count = 0;
boolean handlesWebUris = false;
ArraySet<String> domains = new ArraySet<>();
final boolean previouslyVerified;
boolean hostSetExpanded = false;
boolean needToRunVerify = false;
// If this is a new install and we see that we've already run verification for this
// package, we have nothing to do: it means the state was restored from backup.
IntentFilterVerificationInfo ivi =
mSettings.getIntentFilterVerificationLPr(packageName);
previouslyVerified = (ivi != null);
if (!replacing && previouslyVerified) {
mConnection.infoLog("Package " + packageName + " already verified: status="
+ ivi.getStatusString());
return false;
}
mConnection.infoLog(" Previous verified hosts: "
+ (ivi == null ? "[none]" : ivi.getDomainsString()));
// If any filters need to be verified, then all need to be. In addition, we need to
// know whether an updating app has any web navigation intent filters, to re-
// examine handling policy even if not re-verifying.
final boolean needsVerification = needsNetworkVerificationLPr(packageName);
mConnection.infoLog(" needsVerification = " + needsVerification);
StringBuilder builder = new StringBuilder();
StringBuilderPrinter printer = new StringBuilderPrinter(builder);
for (ParsedActivity a : activities) {
mConnection.infoLog(" activity = " + a.getClassName());
for (ParsedIntentInfo filter : a.getIntents()) {
builder.setLength(0);
filter.dump(printer, "");
mConnection.infoLog(" filter = " + builder.toString());
mConnection.infoLog(" handlesWebUris = " + filter.handlesWebUris(true));
mConnection.infoLog(" needsVerification = " + filter.needsVerification());
if (filter.handlesWebUris(true)) {
handlesWebUris = true;
}
if (needsVerification && filter.needsVerification()) {
mConnection.debugLog("autoVerify requested, processing all filters");
needToRunVerify = true;
// It's safe to break out here because filter.needsVerification()
// can only be true if filter.handlesWebUris(true) returned true, so
// we've already noted that.
break;
}
}
}
mConnection.infoLog(" needToRunVerify = " + needToRunVerify);
mConnection.infoLog(" previouslyVerified = " + previouslyVerified);
// Compare the new set of recognized hosts if the app is either requesting
// autoVerify or has previously used autoVerify but no longer does.
if (needToRunVerify || previouslyVerified) {
final int verificationId = mIntentFilterVerificationToken++;
for (ParsedActivity a : activities) {
for (ParsedIntentInfo filter : a.getIntents()) {
// Run verification against hosts mentioned in any web-nav intent filter,
// even if the filter matches non-web schemes as well
if (filter.handlesWebUris(false /*onlyWebSchemes*/)) {
mConnection.debugLog("Verification needed for IntentFilter:"
+ filter.toString());
mVerifier.addOneIntentFilterVerification(verifierUid, userId,
verificationId, filter, packageName, mStates);
domains.addAll(filter.getHostsList());
count++;
}
}
}
}
mConnection.infoLog(" Update published hosts: " + domains.toString());
// If we've previously verified this same host set (or a subset), we can trust that
// a current ALWAYS policy is still applicable. If this is the case, we're done.
// (If we aren't in ALWAYS, we want to reverify to allow for apps that had failing
// hosts in their intent filters, then pushed a new apk that removed them and now
// passes.)
//
// Cases:
// + still autoVerify (needToRunVerify):
// - preserve current state if all of: unexpanded, in always
// - otherwise rerun as usual (fall through)
// + no longer autoVerify (alreadyVerified && !needToRunVerify)
// - wipe verification history always
// - preserve current state if all of: unexpanded, in always
hostSetExpanded = !previouslyVerified
|| (ivi != null && !ivi.getDomains().containsAll(domains));
final int currentPolicy =
mSettings.getIntentFilterVerificationStatusLPr(packageName, userId);
final boolean keepCurState = !hostSetExpanded
&& currentPolicy == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
if (needToRunVerify && keepCurState) {
mConnection.infoLog("Host set not expanding + ALWAYS -> no need to reverify");
ivi.setDomains(domains);
mConnection.scheduleWriteSettingsLocked();
return false;
} else if (previouslyVerified && !needToRunVerify) {
// Prior autoVerify state but not requesting it now. Clear autoVerify history,
// and preserve the always policy iff the host set is not expanding.
mSettings.clearIntentFilterVerificationsLocked(packageName, userId, !keepCurState);
return false;
}
if (needToRunVerify && count > 0) {
// app requested autoVerify and has at least one matching intent filter
mConnection.debugLog("Starting " + count
+ " IntentFilter verification" + (count > 1 ? "s" : "")
+ " for userId:" + userId);
return true;
} else {
mConnection.debugLog("No web filters or no new host policy for " + packageName);
return false;
}
});
if (runVerify) {
mVerifier.startVerifications(userId, mStates);
}
}
private boolean needsNetworkVerificationLPr(String packageName) {
IntentFilterVerificationInfo ivi = mSettings.getIntentFilterVerificationLPr(
packageName);
if (ivi == null) {
return true;
}
int status = ivi.getStatus();
switch (status) {
case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED:
case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS:
case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK:
return true;
default:
// Nothing to do
return false;
}
}
public void queueVerifyResult(int id, int verificationCode, List<String> failedDomains) {
if (!mHasVerifier) {
return;
}
mContext.enforceCallingOrSelfPermission(
Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT,
"Only intentfilter verification agents can verify applications");
final Message msg = mHandler.obtainMessage(PackageManagerService.INTENT_FILTER_VERIFIED);
final IntentFilterVerificationResponse
response = new IntentFilterVerificationResponse(
Binder.getCallingUid(), verificationCode, failedDomains);
msg.arg1 = id;
msg.obj = response;
mHandler.sendMessage(msg);
}
public void onFilterVerified(Message msg) {
if (!mHasVerifier) {
return;
}
final int verificationId = msg.arg1;
final IntentFilterVerificationState state = mStates.get(verificationId);
if (state == null) {
mConnection.warnLog("Invalid IntentFilter verification token "
+ verificationId + " received");
return;
}
final int userId = state.getUserId();
mConnection.debugLog("Processing IntentFilter verification with token:"
+ verificationId + " and userId:" + userId);
final IntentFilterVerificationResponse
response =
(IntentFilterVerificationResponse) msg.obj;
state.setVerifierResponse(response.callerUid, response.code);
mConnection.debugLog("IntentFilter verification with token:" + verificationId
+ " and userId:" + userId
+ " is settings verifier response with response code:"
+ response.code);
if (response.code == PackageManager.INTENT_FILTER_VERIFICATION_FAILURE) {
mConnection.debugLog("Domains failing verification: "
+ response.getFailedDomainsString());
}
if (state.isVerificationComplete()) {
receiveVerificationResponse(verificationId);
} else {
mConnection.debugLog("IntentFilter verification with token:" + verificationId
+ " was not said to be complete");
}
}
public void receiveVerificationResponse(int verificationId) {
IntentFilterVerificationState ivs = mStates.get(verificationId);
final boolean verified = ivs.isVerified();
ArrayList<ParsedIntentInfo> filters = ivs.getFilters();
final int count = filters.size();
mConnection.debugLog("Received verification response " + verificationId
+ " for " + count + " filters, verified=" + verified);
for (int n = 0; n < count; n++) {
ParsedIntentInfo filter = filters.get(n);
filter.setVerified(verified);
mConnection.debugLog("IntentFilter " + filter.toString()
+ " verified with result:" + verified + " and hosts:"
+ ivs.getHostsString());
}
mStates.remove(verificationId);
final String packageName = ivs.getPackageName();
IntentFilterVerificationInfo ivi = mSettings.getIntentFilterVerificationLPr(packageName);
if (ivi == null) {
mConnection.warnLog("IntentFilterVerificationInfo not found for verificationId:"
+ verificationId + " packageName:" + packageName);
return;
}
mConnection.lock(() -> {
if (verified) {
ivi.setStatus(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS);
} else {
ivi.setStatus(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK);
}
mConnection.scheduleWriteSettingsLocked();
updateUser(packageName, ivs.getUserId(), verified);
});
}
private void updateUser(String packageName, @UserIdInt int userId, boolean verified) {
if (userId == UserHandle.USER_ALL) {
mConnection.infoLog("autoVerify ignored when installing for all users");
return;
}
final int userStatus = mSettings.getIntentFilterVerificationStatusLPr(packageName, userId);
int updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
boolean needUpdate = false;
// In a success case, we promote from undefined or ASK to ALWAYS. This
// supports a flow where the app fails validation but then ships an updated
// APK that passes, and therefore deserves to be in ALWAYS.
//
// If validation failed, the undefined state winds up in the basic ASK behavior,
// but apps that previously passed and became ALWAYS are *demoted* out of
// that state, since they would not deserve the ALWAYS behavior in case of a
// clean install.
switch (userStatus) {
case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS:
if (!verified) {
// Don't demote if sysconfig says 'always'
SystemConfig systemConfig = SystemConfig.getInstance();
ArraySet<String> packages = systemConfig.getLinkedApps();
if (!packages.contains(packageName)) {
// updatedStatus is already UNDEFINED
needUpdate = true;
mConnection.debugLog(
"Formerly validated but now failing; demoting");
} else {
mConnection.debugLog("Updating bundled package " + packageName
+ " failed autoVerify, but sysconfig supersedes");
// leave needUpdate == false here intentionally
}
}
break;
case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED:
// Stay in 'undefined' on verification failure
if (verified) {
updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
}
needUpdate = true;
mConnection.debugLog("Applying update; old=" + userStatus
+ " new=" + updatedStatus);
break;
case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK:
// Keep in 'ask' on failure
if (verified) {
updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
needUpdate = true;
}
break;
// Nothing to do
}
if (needUpdate) {
mSettings.updateIntentFilterVerificationStatusLPw(packageName, updatedStatus, userId);
mConnection.scheduleWritePackageRestrictionsLocked(userId);
}
}
public void primeDomainVerificationsLPw(int userId, Map<String, AndroidPackage> packages) {
if (!mHasVerifier) {
return;
}
mConnection.debugLog("Priming domain verifications in user " + userId);
ArraySet<String> packageNames = mSystemConfig.getLinkedApps();
for (int pkgNameIndex = 0; pkgNameIndex < packageNames.size(); pkgNameIndex++) {
String packageName = packageNames.valueAt(pkgNameIndex);
AndroidPackage pkg = packages.get(packageName);
if (pkg == null) {
mConnection.warnLog("Unknown package " + packageName + " in sysconfig <app-link>");
continue;
} else if (!pkg.isSystem()) {
mConnection.warnLog("Non-system app '" + packageName + "' in sysconfig <app-link>");
continue;
}
ArraySet<String> domains = null;
List<ParsedActivity> activities = pkg.getActivities();
for (int activityIndex = 0; activityIndex < activities.size(); activityIndex++) {
List<ParsedIntentInfo> intentInfos = activities.get(activityIndex).getIntents();
for (int infoIndex = 0; infoIndex < intentInfos.size(); infoIndex++) {
ParsedIntentInfo intentInfo = intentInfos.get(infoIndex);
if (IntentVerifyUtils.hasValidDomains(intentInfo)) {
domains = ArrayUtils.addAll(domains, intentInfo.getHostsList());
}
}
}
if (CollectionUtils.isEmpty(domains)) {
mConnection.warnLog("Sysconfig <app-link> package '" + packageName
+ "' does not handle web links");
continue;
}
mConnection.verboseLog(" + " + packageName);
// 'Undefined' in the global IntentFilterVerificationInfo, i.e. the usual
// state w.r.t. the formal app-linkage "no verification attempted" state;
// and then 'always' in the per-user state actually used for intent resolution.
final IntentFilterVerificationInfo ivi;
ivi = mSettings.createIntentFilterVerificationIfNeededLPw(packageName, domains);
ivi.setStatus(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED);
mSettings.updateIntentFilterVerificationStatusLPw(packageName,
INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS, userId);
}
mConnection.scheduleWritePackageRestrictionsLocked(userId);
mConnection.scheduleWriteSettingsLocked();
}
public IntentFilterVerificationInfo updatePackageSetting(@NonNull PackageSetting pkgSetting,
ArraySet<String> domainSet) {
return mSettings.updatePackageSetting(pkgSetting, domainSet);
}
@NonNull
public ParceledListSlice<IntentFilterVerificationInfo> getIntentFilterVerifications(
@NonNull String packageName) {
return mSettings.getIntentFilterVerifications(packageName);
}
public int getIntentVerificationStatus(@NonNull String packageName, int userId) {
return mSettings.getIntentVerificationStatus(packageName, userId);
}
public boolean updateIntentVerificationStatus(@NonNull String packageName, int status,
int userId) {
return mSettings.updateIntentVerificationStatus(packageName, status, userId);
}
public void clearIntentFilterVerificationsLocked(@NonNull String packageName, int userId,
boolean alsoResetStatus) {
mSettings.clearIntentFilterVerificationsLocked(packageName, userId, alsoResetStatus);
}
public void clearIntentFilterVerificationsLocked(int userId,
WatchedArrayMap<String, AndroidPackage> packages) {
mSettings.clearIntentFilterVerificationsLocked(userId, packages);
}
public void writeAllDomainVerificationsLPr(TypedXmlSerializer serializer, int userId,
@NonNull Map<String, PackageSetting> pkgSettings) throws IOException {
mSettings.writeAllDomainVerificationsLPr(serializer, userId, pkgSettings);
}
public void readAllDomainVerificationsLPr(TypedXmlPullParser parser, @UserIdInt int userId)
throws IOException, XmlPullParserException {
mSettings.readAllDomainVerificationsLPr(parser, userId);
}
public void writeDomainVerificationsLPr(@NonNull TypedXmlSerializer serializer,
@NonNull IntentFilterVerificationInfo info) throws IOException {
mSettings.writeDomainVerificationsLPr(serializer, info);
}
@Nullable
public IntentFilterVerificationInfo getRestoredIntentFilterVerificationInfo(
@NonNull String packageName) {
return mSettings.getRestoredIntentFilterVerificationInfo(packageName);
}
public void readRestoredIntentFilterVerifications(@NonNull TypedXmlPullParser parser)
throws IOException, XmlPullParserException {
mSettings.readRestoredIntentFilterVerifications(parser);
}
public void writeRestoredIntentFilterVerifications(@NonNull TypedXmlSerializer serializer)
throws IOException {
mSettings.writeRestoredIntentFilterVerifications(serializer);
}
@NonNull
public IntentFilterVerificationInfo readDomainVerificationLPw(
@NonNull TypedXmlPullParser parser)
throws IOException, XmlPullParserException {
return mSettings.readDomainVerificationLPw(parser);
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2020 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.intent.verify.legacy;
import android.content.pm.parsing.component.ParsedActivity;
import java.util.List;
public class IntentFilterVerificationParams {
String packageName;
boolean hasDomainUrls;
List<ParsedActivity> activities;
boolean replacing;
int userId;
int verifierUid;
public IntentFilterVerificationParams(String packageName, boolean hasDomainUrls,
List<ParsedActivity> activities, boolean _replacing,
int _userId, int _verifierUid) {
this.packageName = packageName;
this.hasDomainUrls = hasDomainUrls;
this.activities = activities;
replacing = _replacing;
userId = _userId;
verifierUid = _verifierUid;
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright (C) 2020 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.intent.verify.legacy;
import java.util.List;
public class IntentFilterVerificationResponse {
public final int callerUid;
public final int code;
public final List<String> failedDomains;
public IntentFilterVerificationResponse(int callerUid, int code, List<String> failedDomains) {
this.callerUid = callerUid;
this.code = code;
this.failedDomains = failedDomains;
}
public String getFailedDomainsString() {
StringBuilder sb = new StringBuilder();
for (String domain : failedDomains) {
if (sb.length() > 0) {
sb.append(" ");
}
sb.append(domain);
}
return sb.toString();
}
}

View File

@@ -0,0 +1,393 @@
/*
* Copyright (C) 2020 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.intent.verify.legacy;
import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Context;
import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.ParceledListSlice;
import android.os.Binder;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.SparseIntArray;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.XmlUtils;
import com.android.server.SystemConfig;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.PackageSetting;
import com.android.server.pm.Settings;
import com.android.server.pm.UserManagerService;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.utils.WatchedArrayMap;
import com.android.server.utils.WatchedSparseIntArray;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class IntentFilterVerificationSettings {
private final Context mContext;
private final IntentVerifierProxy.PackageManagerServiceConnection mConnection;
private final UserManagerService mUserManagerService;
// Set of restored intent-filter verification states
final ArrayMap<String, IntentFilterVerificationInfo> mRestoredIntentFilterVerifications =
new ArrayMap<>();
public IntentFilterVerificationSettings(Context context,
UserManagerService userManagerService,
IntentVerifierProxy.PackageManagerServiceConnection connection) {
mContext = context;
mConnection = connection;
mUserManagerService = userManagerService;
}
public int getIntentVerificationStatus(@NonNull String packageName, int userId) {
final int callingUid = Binder.getCallingUid();
if (UserHandle.getUserId(callingUid) != userId) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
"getIntentVerificationStatus" + userId);
}
if (mConnection.getInstantAppPackageName(callingUid) != null) {
return INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
}
return mConnection.lockReturn(() -> {
final PackageSetting ps = mConnection.getPackageSettingLPr(packageName);
if (ps == null
|| mConnection.shouldFilterApplicationLocked(
ps, callingUid, UserHandle.getUserId(callingUid))) {
return INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
}
return getIntentFilterVerificationStatusLPr(packageName, userId);
});
}
public boolean updateIntentVerificationStatus(@NonNull String packageName, int status,
int userId) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
boolean result = mConnection.lockReturn(() -> {
final PackageSetting ps = mConnection.getPackageSettingLPr(packageName);
if (mConnection.shouldFilterApplicationLocked(
ps, Binder.getCallingUid(), UserHandle.getCallingUserId())) {
return false;
}
return updateIntentFilterVerificationStatusLPw(packageName, status, userId);
});
if (result) {
mConnection.scheduleWritePackageRestrictionsLocked(userId);
}
return result;
}
@NonNull
public ParceledListSlice<IntentFilterVerificationInfo> getIntentFilterVerifications(
@NonNull String packageName) {
final int callingUid = Binder.getCallingUid();
if (mConnection.getInstantAppPackageName(callingUid) != null) {
return ParceledListSlice.emptyList();
}
return mConnection.lockReturn(() -> {
final PackageSetting ps = mConnection.getPackageSettingLPr(packageName);
if (mConnection.shouldFilterApplicationLocked(ps, callingUid,
UserHandle.getUserId(callingUid))) {
return ParceledListSlice.emptyList();
}
return new ParceledListSlice<>(getIntentFilterVerificationsLPr(packageName));
});
}
/** This method takes a specific user id as well as UserHandle.USER_ALL. */
public void clearIntentFilterVerificationsLocked(int userId,
WatchedArrayMap<String, AndroidPackage> packages) {
final int packageCount = packages.size();
for (int i = 0; i < packageCount; i++) {
AndroidPackage pkg = packages.valueAt(i);
clearIntentFilterVerificationsLocked(pkg.getPackageName(), userId, true);
}
}
/** This method takes a specific user id as well as UserHandle.USER_ALL. */
public void clearIntentFilterVerificationsLocked(String packageName, int userId,
boolean alsoResetStatus) {
if (SystemConfig.getInstance().getLinkedApps().contains(packageName)) {
// Nope, need to preserve the system configuration approval for this app
return;
}
if (userId == UserHandle.USER_ALL) {
if (removeIntentFilterVerificationLPw(packageName, mUserManagerService.getUserIds())) {
for (int oneUserId : mUserManagerService.getUserIds()) {
mConnection.scheduleWritePackageRestrictionsLocked(oneUserId);
}
}
} else {
if (removeIntentFilterVerificationLPw(packageName, userId, alsoResetStatus)) {
mConnection.scheduleWritePackageRestrictionsLocked(userId);
}
}
}
@Nullable
public IntentFilterVerificationInfo createIntentFilterVerificationIfNeededLPw(
String packageName, ArraySet<String> domains) {
PackageSetting pkgSetting = mConnection.getPackageSettingLPr(packageName);
if (pkgSetting == null) {
mConnection.warnLog("No package known: " + packageName);
return null;
}
return updatePackageSetting(pkgSetting, domains);
}
public IntentFilterVerificationInfo updatePackageSetting(@NonNull PackageSetting pkgSetting,
ArraySet<String> domains) {
String pkgName = pkgSetting.name;
IntentFilterVerificationInfo ivi = pkgSetting.getIntentFilterVerificationInfo();
if (ivi == null) {
ivi = new IntentFilterVerificationInfo(pkgName, domains);
pkgSetting.setIntentFilterVerificationInfo(ivi);
mConnection.debugLog("Creating new IntentFilterVerificationInfo for pkg: " + pkgName);
} else {
ivi.setDomains(domains);
mConnection.debugLog(
"Setting domains to existing IntentFilterVerificationInfo for pkg: " +
pkgName + " and with domains: " + ivi.getDomainsString());
}
return ivi;
}
public int getIntentFilterVerificationStatusLPr(@NonNull String packageName,
@UserIdInt int userId) {
PackageSetting pkgSetting = mConnection.getPackageSettingLPr(packageName);
if (pkgSetting == null) {
mConnection.warnLog("No package known: " + packageName);
return INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
}
return (int) (pkgSetting.getDomainVerificationStatusForUser(userId) >> 32);
}
@Nullable
public IntentFilterVerificationInfo getIntentFilterVerificationLPr(
@NonNull String packageName) {
PackageSetting ps = mConnection.getPackageSettingLPr(packageName);
if (ps == null) {
mConnection.warnLog("No package known: " + packageName);
return null;
}
return ps.getIntentFilterVerificationInfo();
}
boolean updateIntentFilterVerificationStatusLPw(String packageName, final int status,
int userId) {
// Update the status for the current package
PackageSetting current = mConnection.getPackageSettingLPr(packageName);
if (current == null) {
mConnection.warnLog("No package known: " + packageName);
return false;
}
final int alwaysGeneration;
if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) {
WatchedSparseIntArray nextAppLinkGeneration = mConnection.getNextAppLinkGeneration();
alwaysGeneration = nextAppLinkGeneration.get(userId) + 1;
nextAppLinkGeneration.put(userId, alwaysGeneration);
} else {
alwaysGeneration = 0;
}
current.setDomainVerificationStatusForUser(status, alwaysGeneration, userId);
return true;
}
private boolean removeIntentFilterVerificationLPw(String packageName, int userId,
boolean alsoResetStatus) {
PackageSetting ps = mConnection.getPackageSettingLPr(packageName);
if (ps == null) {
mConnection.warnLog("No package known: " + packageName);
return false;
}
if (alsoResetStatus) {
ps.clearDomainVerificationStatusForUser(userId);
}
ps.setIntentFilterVerificationInfo(null);
return true;
}
private boolean removeIntentFilterVerificationLPw(String packageName, int[] userIds) {
boolean result = false;
for (int userId : userIds) {
result |= removeIntentFilterVerificationLPw(packageName, userId, true);
}
return result;
}
private List<IntentFilterVerificationInfo> getIntentFilterVerificationsLPr(
String packageName) {
if (packageName == null) {
return Collections.emptyList();
}
ArrayList<IntentFilterVerificationInfo> result = new ArrayList<>();
for (PackageSetting ps : mConnection.getPackageSettingsLPr().values()) {
IntentFilterVerificationInfo ivi = ps.getIntentFilterVerificationInfo();
if (ivi == null || TextUtils.isEmpty(ivi.getPackageName()) ||
!ivi.getPackageName().equalsIgnoreCase(packageName)) {
continue;
}
result.add(ivi);
}
return result;
}
// Specifically for backup/restore
public void writeAllDomainVerificationsLPr(TypedXmlSerializer serializer, int userId,
@NonNull Map<String, PackageSetting> pkgSettings)
throws IllegalArgumentException, IllegalStateException, IOException {
serializer.startTag(null, Settings.TAG_ALL_INTENT_FILTER_VERIFICATION);
for (PackageSetting value : pkgSettings.values()) {
IntentFilterVerificationInfo ivi = value.getIntentFilterVerificationInfo();
if (ivi != null) {
writeDomainVerificationsLPr(serializer, ivi);
}
}
serializer.endTag(null, Settings.TAG_ALL_INTENT_FILTER_VERIFICATION);
}
public void writeDomainVerificationsLPr(TypedXmlSerializer serializer,
IntentFilterVerificationInfo verificationInfo)
throws IllegalArgumentException, IllegalStateException, IOException {
if (verificationInfo != null && verificationInfo.getPackageName() != null) {
serializer.startTag(null, Settings.TAG_DOMAIN_VERIFICATION);
verificationInfo.writeToXml(serializer);
mConnection.debugLog("Wrote domain verification for package: "
+ verificationInfo.getPackageName());
serializer.endTag(null, Settings.TAG_DOMAIN_VERIFICATION);
}
}
// Specifically for backup/restore
public void readAllDomainVerificationsLPr(TypedXmlPullParser parser, int userId)
throws XmlPullParserException, IOException {
mRestoredIntentFilterVerifications.clear();
int outerDepth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
String tagName = parser.getName();
if (tagName.equals(Settings.TAG_DOMAIN_VERIFICATION)) {
IntentFilterVerificationInfo ivi = new IntentFilterVerificationInfo(parser);
final String pkgName = ivi.getPackageName();
final PackageSetting ps = mConnection.getPackageSettingLPr(pkgName);
if (ps != null) {
// known/existing package; update in place
ps.setIntentFilterVerificationInfo(ivi);
mConnection.debugLog("Restored IVI for existing app " + pkgName
+ " status=" + ivi.getStatusString());
} else {
mRestoredIntentFilterVerifications.put(pkgName, ivi);
mConnection.debugLog("Restored IVI for pending app " + pkgName
+ " status=" + ivi.getStatusString());
}
} else {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Unknown element under <all-intent-filter-verification>: "
+ parser.getName());
XmlUtils.skipCurrentTag(parser);
}
}
}
public IntentFilterVerificationInfo getRestoredIntentFilterVerificationInfo(
@NonNull String packageName) {
IntentFilterVerificationInfo info = mRestoredIntentFilterVerifications.remove(packageName);
if (info != null) {
mConnection.infoLog(
"Applying restored IVI for " + packageName + " : " + info.getStatusString());
}
return info;
}
public void readRestoredIntentFilterVerifications(@NonNull TypedXmlPullParser parser)
throws IOException, XmlPullParserException {
int outerDepth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
final String tagName = parser.getName();
if (tagName.equals(Settings.TAG_DOMAIN_VERIFICATION)) {
IntentFilterVerificationInfo ivi = new IntentFilterVerificationInfo(parser);
mConnection.infoLog("Restored IVI for " + ivi.getPackageName()
+ " status=" + ivi.getStatusString());
mRestoredIntentFilterVerifications.put(ivi.getPackageName(), ivi);
} else {
mConnection.warnLog("Unknown element: " + tagName);
XmlUtils.skipCurrentTag(parser);
}
}
}
public void writeRestoredIntentFilterVerifications(@NonNull TypedXmlSerializer serializer)
throws IOException {
final int numIVIs = mRestoredIntentFilterVerifications.size();
if (numIVIs > 0) {
mConnection.infoLog("Writing restored-ivi entries to packages.xml");
serializer.startTag(null, "restored-ivi");
for (int i = 0; i < numIVIs; i++) {
IntentFilterVerificationInfo ivi = mRestoredIntentFilterVerifications.valueAt(i);
writeDomainVerificationsLPr(serializer, ivi);
}
serializer.endTag(null, "restored-ivi");
} else {
mConnection.infoLog(" no restored IVI entries to write");
}
}
@NonNull
public IntentFilterVerificationInfo readDomainVerificationLPw(
@NonNull TypedXmlPullParser parser)
throws IOException, XmlPullParserException {
return new IntentFilterVerificationInfo(parser);
}
}

View File

@@ -0,0 +1,130 @@
/*
* Copyright (C) 2020 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.intent.verify.legacy;
import android.content.pm.PackageManager;
import android.content.pm.parsing.component.ParsedIntentInfo;
import android.util.ArraySet;
import android.util.Slog;
import java.util.ArrayList;
public class IntentFilterVerificationState {
static final String TAG = IntentFilterVerificationState.class.getName();
public static final int STATE_UNDEFINED = 0;
public static final int STATE_VERIFICATION_PENDING = 1;
public static final int STATE_VERIFICATION_SUCCESS = 2;
public static final int STATE_VERIFICATION_FAILURE = 3;
private int mRequiredVerifierUid = 0;
private int mState;
private ArrayList<ParsedIntentInfo> mFilters = new ArrayList<>();
private ArraySet<String> mHosts = new ArraySet<>();
private int mUserId;
private String mPackageName;
private boolean mVerificationComplete;
public IntentFilterVerificationState(int verifierUid, int userId, String packageName) {
mRequiredVerifierUid = verifierUid;
mUserId = userId;
mPackageName = packageName;
mState = STATE_UNDEFINED;
mVerificationComplete = false;
}
public void setState(int state) {
if (state > STATE_VERIFICATION_FAILURE || state < STATE_UNDEFINED) {
mState = STATE_UNDEFINED;
} else {
mState = state;
}
}
public int getState() {
return mState;
}
public void setPendingState() {
setState(STATE_VERIFICATION_PENDING);
}
public ArrayList<ParsedIntentInfo> getFilters() {
return mFilters;
}
public boolean isVerificationComplete() {
return mVerificationComplete;
}
public boolean isVerified() {
if (mVerificationComplete) {
return (mState == STATE_VERIFICATION_SUCCESS);
}
return false;
}
public int getUserId() {
return mUserId;
}
public String getPackageName() {
return mPackageName;
}
public String getHostsString() {
StringBuilder sb = new StringBuilder();
final int count = mHosts.size();
for (int i = 0; i < count; i++) {
if (i > 0) {
sb.append(" ");
}
String host = mHosts.valueAt(i);
// "*.example.tld" is validated via https://example.tld
if (host.startsWith("*.")) {
host = host.substring(2);
}
sb.append(host);
}
return sb.toString();
}
public boolean setVerifierResponse(int callerUid, int code) {
if (mRequiredVerifierUid == callerUid) {
int state = STATE_UNDEFINED;
if (code == PackageManager.INTENT_FILTER_VERIFICATION_SUCCESS) {
state = STATE_VERIFICATION_SUCCESS;
} else if (code == PackageManager.INTENT_FILTER_VERIFICATION_FAILURE) {
state = STATE_VERIFICATION_FAILURE;
}
mVerificationComplete = true;
setState(state);
return true;
}
Slog.d(TAG, "Cannot set verifier response with callerUid:" + callerUid + " and code:"
+ code + " as required verifierUid is:" + mRequiredVerifierUid);
return false;
}
public void addFilter(ParsedIntentInfo filter) {
mFilters.add(filter);
mHosts.addAll(filter.getHostsList());
}
}

View File

@@ -0,0 +1,203 @@
/*
* Copyright (C) 2020 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.intent.verify.legacy;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.BroadcastOptions;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.parsing.component.ParsedIntentInfo;
import android.os.Process;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.SparseArray;
import android.util.SparseIntArray;
import com.android.server.DeviceIdleInternal;
import com.android.server.pm.PackageSetting;
import com.android.server.utils.WatchedSparseIntArray;
import java.util.ArrayList;
import java.util.Map;
import java.util.function.Supplier;
public class IntentVerifierProxy {
private final Context mContext;
private final PackageManagerServiceConnection mConnection;
private final ArrayList<Integer> mCurrentIntentFilterVerifications = new ArrayList<>();
@Nullable
private ComponentName mIntentFilterVerifierComponent;
public IntentVerifierProxy(Context context, PackageManagerServiceConnection connection) {
mConnection = connection;
mContext = context;
}
private String getDefaultScheme() {
return IntentFilter.SCHEME_HTTPS;
}
public void setComponent(@Nullable ComponentName componentName) {
this.mIntentFilterVerifierComponent = componentName;
}
@Nullable
public ComponentName getComponent() {
return mIntentFilterVerifierComponent;
}
public void startVerifications(int userId, SparseArray<IntentFilterVerificationState> states) {
if (mIntentFilterVerifierComponent == null) {
return;
}
// Launch verifications requests
int count = mCurrentIntentFilterVerifications.size();
for (int n = 0; n < count; n++) {
int verificationId = mCurrentIntentFilterVerifications.get(n);
final IntentFilterVerificationState ivs = states.get(verificationId);
String packageName = ivs.getPackageName();
ArrayList<ParsedIntentInfo> filters = ivs.getFilters();
final int filterCount = filters.size();
ArraySet<String> domainsSet = new ArraySet<>();
for (int m = 0; m < filterCount; m++) {
ParsedIntentInfo filter = filters.get(m);
domainsSet.addAll(filter.getHostsList());
}
mConnection.writeSettings(packageName, domainsSet);
sendVerificationRequest(verificationId, ivs);
}
mCurrentIntentFilterVerifications.clear();
}
private void sendVerificationRequest(int verificationId, IntentFilterVerificationState ivs) {
Intent verificationIntent = new Intent(Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION);
verificationIntent.putExtra(
PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_ID,
verificationId);
verificationIntent.putExtra(
PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_URI_SCHEME,
getDefaultScheme());
verificationIntent.putExtra(
PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_HOSTS,
ivs.getHostsString());
verificationIntent.putExtra(
PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME,
ivs.getPackageName());
verificationIntent.setComponent(mIntentFilterVerifierComponent);
verificationIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
final long allowListTimeout = mConnection.getVerificationTimeout();
final BroadcastOptions options = BroadcastOptions.makeBasic();
options.setTemporaryAppWhitelistDuration(allowListTimeout);
mConnection.getDeviceIdleInternal().addPowerSaveTempWhitelistApp(Process.myUid(),
mIntentFilterVerifierComponent.getPackageName(), allowListTimeout,
UserHandle.USER_SYSTEM, true, "intent filter verifier");
mContext.sendBroadcastAsUser(verificationIntent, UserHandle.SYSTEM,
null, options.toBundle());
mConnection.debugLog("Sending IntentFilter verification broadcast");
}
public boolean addOneIntentFilterVerification(int verifierUid, int userId, int verificationId,
ParsedIntentInfo filter, String packageName,
SparseArray<IntentFilterVerificationState> states) {
if (!IntentVerifyUtils.hasValidDomains(filter)) {
return false;
}
IntentFilterVerificationState ivs = states.get(verificationId);
if (ivs == null) {
ivs = createDomainVerificationState(verifierUid, userId, verificationId,
packageName, states);
}
mConnection.debugLog("Adding verification filter for " + packageName + ": " + filter);
ivs.addFilter(filter);
return true;
}
private IntentFilterVerificationState createDomainVerificationState(int verifierUid,
int userId, int verificationId, String packageName,
SparseArray<IntentFilterVerificationState> states) {
IntentFilterVerificationState
ivs = new IntentFilterVerificationState(
verifierUid, userId, packageName);
ivs.setPendingState();
mConnection.lock(() -> {
states.append(verificationId, ivs);
mCurrentIntentFilterVerifications.add(verificationId);
});
return ivs;
}
public interface PackageManagerServiceConnection {
void lock(Runnable block);
<T> T lockReturn(Supplier<T> block);
void debugLog(String message);
void verboseLog(String message);
void warnLog(String message);
void infoLog(String message);
void writeSettings(String packageName, ArraySet<String> domainsSet);
// Seems this is used when an IFVI object is mutated, and it's assumed that the same object
// ends up written to disk.
void scheduleWriteSettingsLocked();
long getVerificationTimeout();
void scheduleWritePackageRestrictionsLocked(@UserIdInt int userId);
String getInstantAppPackageName(int callingUid);
@Nullable
PackageSetting getPackageSettingLPr(@NonNull String packageName);
@NonNull
Map<String, PackageSetting> getPackageSettingsLPr();
boolean shouldFilterApplicationLocked(PackageSetting ps, int callingUid,
@UserIdInt int userId);
int getPackageUid(String packageName, int flags, @UserIdInt int userId);
@NonNull
WatchedSparseIntArray getNextAppLinkGeneration();
/**
* DeviceIdleInternal has a dependency on PackageManager, so it can't be passed in at
* initialization. It has to be accessed at use time.
*/
@NonNull
DeviceIdleInternal getDeviceIdleInternal();
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (C) 2020 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.intent.verify.legacy;
import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.parsing.component.ParsedIntentInfo;
import com.android.server.pm.PackageSetting;
public class IntentVerifyUtils {
public static boolean hasValidDomains(ParsedIntentInfo filter) {
return filter.hasCategory(Intent.CATEGORY_BROWSABLE)
&& (filter.hasDataScheme(IntentFilter.SCHEME_HTTP) ||
filter.hasDataScheme(IntentFilter.SCHEME_HTTPS));
}
// Returns a packed value as a long:
//
// high 'int'-sized word: link status: undefined/ask/never/always.
// low 'int'-sized word: relative priority among 'always' results.
public static long getDomainVerificationStatus(PackageSetting ps, int userId) {
long result = ps.getDomainVerificationStatusForUser(userId);
// if none available, get the status
if (result >> 32 == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) {
if (ps.getIntentFilterVerificationInfo() != null) {
result = ((long) ps.getIntentFilterVerificationInfo().getStatus()) << 32;
}
}
return result;
}
}

View File

@@ -36,7 +36,6 @@ import static org.junit.Assert.fail;
import android.annotation.NonNull;
import android.app.PropertyInvalidatedCache;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
@@ -59,6 +58,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.permission.persistence.RuntimePermissionsPersistence;
import com.android.server.LocalServices;
import com.android.server.pm.intent.verify.legacy.IntentFilterVerificationManager;
import com.android.server.pm.parsing.pkg.PackageImpl;
import com.android.server.pm.parsing.pkg.ParsedPackage;
import com.android.server.pm.permission.LegacyPermissionDataProvider;
@@ -94,6 +94,8 @@ public class PackageManagerSettingsTests {
RuntimePermissionsPersistence mRuntimePermissionsPersistence;
@Mock
LegacyPermissionDataProvider mPermissionDataProvider;
@Mock
IntentFilterVerificationManager mIntentFilterVerificationManager;
@Before
public void initializeMocks() {
@@ -112,10 +114,7 @@ public class PackageManagerSettingsTests {
throws ReflectiveOperationException, IllegalAccessException {
/* write out files and read */
writeOldFiles();
final Context context = InstrumentationRegistry.getContext();
final Object lock = new Object();
Settings settings = new Settings(context.getFilesDir(),
mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
Settings settings = makeSettings();
assertThat(settings.readLPw(createFakeUsers()), is(true));
verifyKeySetMetaData(settings);
}
@@ -126,10 +125,7 @@ public class PackageManagerSettingsTests {
throws ReflectiveOperationException, IllegalAccessException {
// write out files and read
writeOldFiles();
final Context context = InstrumentationRegistry.getContext();
final Object lock = new Object();
Settings settings = new Settings(context.getFilesDir(),
mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
Settings settings = makeSettings();
assertThat(settings.readLPw(createFakeUsers()), is(true));
// write out, read back in and verify the same
@@ -142,10 +138,7 @@ public class PackageManagerSettingsTests {
public void testSettingsReadOld() {
// Write delegateshellthe package files and make sure they're parsed properly the first time
writeOldFiles();
final Context context = InstrumentationRegistry.getContext();
final Object lock = new Object();
Settings settings = new Settings(context.getFilesDir(),
mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
Settings settings = makeSettings();
assertThat(settings.readLPw(createFakeUsers()), is(true));
assertThat(settings.getPackageLPr(PACKAGE_NAME_3), is(notNullValue()));
assertThat(settings.getPackageLPr(PACKAGE_NAME_1), is(notNullValue()));
@@ -164,16 +157,12 @@ public class PackageManagerSettingsTests {
public void testNewPackageRestrictionsFile() throws ReflectiveOperationException {
// Write the package files and make sure they're parsed properly the first time
writeOldFiles();
final Context context = InstrumentationRegistry.getContext();
final Object lock = new Object();
Settings settings = new Settings(context.getFilesDir(),
mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
Settings settings = makeSettings();
assertThat(settings.readLPw(createFakeUsers()), is(true));
settings.writeLPr();
// Create Settings again to make it read from the new files
settings = new Settings(context.getFilesDir(),
mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
settings = makeSettings();
assertThat(settings.readLPw(createFakeUsers()), is(true));
PackageSetting ps = settings.getPackageLPr(PACKAGE_NAME_2);
@@ -200,10 +189,7 @@ public class PackageManagerSettingsTests {
@Test
public void testReadPackageRestrictions_noSuspendingPackage() {
writePackageRestrictions_noSuspendingPackageXml(0);
final Object lock = new Object();
final Context context = InstrumentationRegistry.getTargetContext();
final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, null,
lock);
Settings settingsUnderTest = makeSettings();
final WatchableTester watcher =
new WatchableTester(settingsUnderTest, "noSuspendingPackage");
watcher.register();
@@ -244,10 +230,7 @@ public class PackageManagerSettingsTests {
@Test
public void testReadPackageRestrictions_noSuspendParamsMap() {
writePackageRestrictions_noSuspendParamsMapXml(0);
final Object lock = new Object();
final Context context = InstrumentationRegistry.getTargetContext();
final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, null,
lock);
final Settings settingsUnderTest = makeSettings();
final WatchableTester watcher =
new WatchableTester(settingsUnderTest, "noSuspendParamsMap");
watcher.register();
@@ -281,9 +264,7 @@ public class PackageManagerSettingsTests {
@Test
public void testReadWritePackageRestrictions_suspendInfo() {
final Context context = InstrumentationRegistry.getTargetContext();
final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, null,
new Object());
final Settings settingsUnderTest = makeSettings();
final WatchableTester watcher = new WatchableTester(settingsUnderTest, "suspendInfo");
watcher.register();
final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
@@ -397,9 +378,7 @@ public class PackageManagerSettingsTests {
@Test
public void testReadWritePackageRestrictions_distractionFlags() {
final Context context = InstrumentationRegistry.getTargetContext();
final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, null,
new Object());
final Settings settingsUnderTest = makeSettings();
final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
final PackageSetting ps2 = createPackageSetting(PACKAGE_NAME_2);
final PackageSetting ps3 = createPackageSetting(PACKAGE_NAME_3);
@@ -440,10 +419,7 @@ public class PackageManagerSettingsTests {
@Test
public void testWriteReadUsesStaticLibraries() {
final Context context = InstrumentationRegistry.getTargetContext();
final Object lock = new Object();
final Settings settingsUnderTest = new Settings(context.getFilesDir(),
mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
final Settings settingsUnderTest = makeSettings();
final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
ps1.appId = Process.FIRST_APPLICATION_UID;
ps1.pkg = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME_1).hideAsParsed())
@@ -516,10 +492,7 @@ public class PackageManagerSettingsTests {
public void testEnableDisable() {
// Write the package files and make sure they're parsed properly the first time
writeOldFiles();
final Context context = InstrumentationRegistry.getContext();
final Object lock = new Object();
Settings settings = new Settings(context.getFilesDir(),
mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
Settings settings = makeSettings();
final WatchableTester watcher = new WatchableTester(settings, "testEnableDisable");
watcher.register();
assertThat(settings.readLPw(createFakeUsers()), is(true));
@@ -698,12 +671,9 @@ public class PackageManagerSettingsTests {
/** Update package; changing shared user throws exception */
@Test
public void testUpdatePackageSetting03() {
final Context context = InstrumentationRegistry.getContext();
final Object lock = new Object();
final Settings testSettings01 = new Settings(context.getFilesDir(),
mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
Settings settings = makeSettings();
final SharedUserSetting testUserSetting01 = createSharedUserSetting(
testSettings01, "TestUser", 10064, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/);
settings, "TestUser", 10064, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/);
final PackageSetting testPkgSetting01 =
createPackageSetting(0 /*sharedUserId*/, 0 /*pkgFlags*/);
try {
@@ -808,12 +778,9 @@ public class PackageManagerSettingsTests {
/** Create PackageSetting for a shared user */
@Test
public void testCreateNewSetting03() {
final Context context = InstrumentationRegistry.getContext();
final Object lock = new Object();
final Settings testSettings01 = new Settings(context.getFilesDir(),
mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
Settings settings = makeSettings();
final SharedUserSetting testUserSetting01 = createSharedUserSetting(
testSettings01, "TestUser", 10064, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/);
settings, "TestUser", 10064, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/);
final PackageSetting testPkgSetting01 = Settings.createNewSetting(
PACKAGE_NAME,
null /*originalPkg*/,
@@ -1212,6 +1179,12 @@ public class PackageManagerSettingsTests {
deleteFolder(InstrumentationRegistry.getTargetContext().getFilesDir());
}
private Settings makeSettings() {
return new Settings(InstrumentationRegistry.getContext().getFilesDir(),
mRuntimePermissionsPersistence, mPermissionDataProvider,
mIntentFilterVerificationManager, new Object());
}
private void verifyKeySetMetaData(Settings settings)
throws ReflectiveOperationException, IllegalAccessException {
ArrayMap<String, PackageSetting> packages =