Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 96694aa7 authored by Winson's avatar Winson
Browse files

Attach domain verification states during package scan/install

Handles adding a new package (either from a brand new install or a boot
scan) and migrating between packages when installing an update.

Will merge the package states in the update case to preserve successful
verifications while removing failed domains so that they can be
re-requested by the verification agent.

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

Bug: 163565712

Test: manual, device boots, will be tested as part of later changes

Change-Id: I54dc8415e10544e618905e269ab95ec0a2af0fb0
parent 39ef1f3f
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -14576,6 +14576,12 @@ public class PackageManagerService extends IPackageManager.Stub
            mAppsFilter.addPackage(pkgSetting, isReplace);
            mPackageProperty.addAllProperties(pkg);
            if (oldPkgSetting == null || oldPkgSetting.getPkg() == null) {
                mDomainVerificationManager.addPackage(pkgSetting);
            } else {
                mDomainVerificationManager.migrateState(oldPkgSetting, pkgSetting);
            }
            int collectionSize = ArrayUtils.size(pkg.getInstrumentations());
            StringBuilder r = null;
            int i;
+13 −2
Original line number Diff line number Diff line
@@ -42,6 +42,8 @@ import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;

import com.android.internal.annotations.VisibleForTesting;
import com.android.server.pm.domain.verify.DomainVerificationManagerInternal;
import com.android.server.pm.domain.verify.DomainVerificationService;
import com.android.server.pm.parsing.pkg.AndroidPackage;

import java.io.File;
@@ -350,9 +352,18 @@ public abstract class PackageSettingBase extends SettingBase {
        return readUserState(userId).getSharedLibraryOverlayPaths();
    }

    /** Only use for testing. Do NOT use in production code. */
    /**
     * Only use for testing. Do NOT use in production code.
     *
     * Unless you're {@link DomainVerificationService} and you need to migrate legacy state.
     * This is done rather than passing in the user IDs to
     * {@link DomainVerificationManagerInternal#addPackage(PackageSetting)} to make the v2 APIs
     * completely correct, without legacy details, since that method inherently does not care about
     * the users on the device.
     */
    @VisibleForTesting
    SparseArray<PackageUserState> getUserState() {
    @Deprecated
    public SparseArray<PackageUserState> getUserState() {
        return mUserState;
    }

+39 −0
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.content.pm.domain.verify.DomainVerificationSet;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;

import com.android.server.pm.PackageSetting;
import com.android.server.pm.domain.verify.models.DomainVerificationPkgState;

import org.xmlpull.v1.XmlPullParserException;
@@ -40,6 +41,44 @@ public interface DomainVerificationManagerInternal extends DomainVerificationMan
    @NonNull
    UUID generateNewId();

    /**
     * Restores or creates internal state for the new package. This can either be from scanning a
     * package at boot, or a truly new installation on the device. It is expected that the {@link
     * PackageSetting#getDomainSetId()} already be set to the correct value.
     * <p>
     * If this is from scan, there should be a pending state that was previous read using {@link
     * #readSettings(TypedXmlPullParser)}, which will be attached as-is to the package. In this
     * case, a broadcast will not be sent to the domain verification agent on device, as it is
     * assumed nothing has changed since the device rebooted.
     * <p>
     * If this is a new install, state will be restored from a previous call to {@link
     * #restoreSettings(TypedXmlPullParser)}, or a new one will be generated. In either case, a
     * broadcast will be sent to the domain verification agent so it may re-run any verification
     * logic for the newly associated domains.
     * <p>
     * This will mutate internal {@link DomainVerificationPkgState} and so will hold the internal
     * lock. This should never be called from within the domain verification classes themselves.
     * <p>
     * This will NOT call {@link #writeSettings(TypedXmlSerializer)}. That must be handled by the
     * caller.
     */
    void addPackage(@NonNull PackageSetting newPkgSetting);

    /**
     * Migrates verification state from a previous install to a new one. It is expected that the
     * {@link PackageSetting#getDomainSetId()} already be set to the correct value, usually from
     * {@link #generateNewId()}. This will preserve {@link #STATE_SUCCESS} domains under the
     * assumption that the new package will pass the same server side config as the previous
     * package, as they have matching signatures.
     * <p>
     * This will mutate internal {@link DomainVerificationPkgState} and so will hold the internal
     * lock. This should never be called from within the domain verification classes themselves.
     * <p>
     * This will NOT call {@link #writeSettings(TypedXmlSerializer)}. That must be handled by the
     * caller.
     */
    void migrateState(@NonNull PackageSetting oldPkgSetting, @NonNull PackageSetting newPkgSetting);

    /**
     * Serializes the entire internal state. This is equivalent to a full backup of the existing
     * verification state.
+228 −2
Original line number Diff line number Diff line
@@ -20,18 +20,32 @@ 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.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageUserState;
import android.content.pm.domain.verify.DomainVerificationManager;
import android.content.pm.domain.verify.DomainVerificationSet;
import android.content.pm.domain.verify.DomainVerificationState;
import android.content.pm.domain.verify.DomainVerificationUserSelection;
import android.content.pm.domain.verify.IDomainVerificationManager;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Singleton;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;

import com.android.internal.annotations.GuardedBy;
import com.android.server.SystemConfig;
import com.android.server.SystemService;
import com.android.server.compat.PlatformCompat;
import com.android.server.pm.PackageSetting;
import com.android.server.pm.domain.verify.models.DomainVerificationPkgState;
import com.android.server.pm.domain.verify.models.DomainVerificationStateMap;
import com.android.server.pm.domain.verify.models.DomainVerificationUserState;
import com.android.server.pm.parsing.pkg.AndroidPackage;

import org.xmlpull.v1.XmlPullParserException;

@@ -63,17 +77,25 @@ public class DomainVerificationService extends SystemService
    @NonNull
    private final Singleton<Connection> mConnection;

    @NonNull
    private final SystemConfig mSystemConfig;

    @NonNull
    private final DomainVerificationSettings mSettings;

    @NonNull
    private final DomainVerificationCollector mCollector;

    @NonNull
    private final IDomainVerificationManager.Stub mStub = new DomainVerificationManagerStub(this);

    public DomainVerificationService(@NonNull Context context,
            @NonNull Singleton<Connection> connection) {
    public DomainVerificationService(@NonNull Context context, @NonNull SystemConfig systemConfig,
            @NonNull PlatformCompat platformCompat, @NonNull Singleton<Connection> connection) {
        super(context);
        mConnection = connection;
        mSystemConfig = systemConfig;
        mSettings = new DomainVerificationSettings();
        mCollector = new DomainVerificationCollector(platformCompat, systemConfig);
    }

    @Override
@@ -149,6 +171,206 @@ public class DomainVerificationService extends SystemService
        return UUID.randomUUID();
    }

    @Override
    public void migrateState(@NonNull PackageSetting oldPkgSetting,
            @NonNull PackageSetting newPkgSetting) {
        String pkgName = newPkgSetting.name;
        boolean sendBroadcast;

        synchronized (mLock) {
            UUID oldDomainSetId = oldPkgSetting.getDomainSetId();
            UUID newDomainSetId = newPkgSetting.getDomainSetId();
            DomainVerificationPkgState oldPkgState = mAttachedPkgStates.remove(oldDomainSetId);

            AndroidPackage oldPkg = oldPkgSetting.getPkg();
            AndroidPackage newPkg = newPkgSetting.getPkg();

            ArrayMap<String, Integer> newStateMap = new ArrayMap<>();
            SparseArray<DomainVerificationUserState> newUserStates = new SparseArray<>();

            if (oldPkgState == null || oldPkg == null || newPkg == null) {
                // Should be impossible, but to be safe, continue with a new blank state instead
                Slog.wtf(TAG, "Invalid state nullability old state = " + oldPkgState
                        + ", old pkgSetting = " + oldPkgSetting
                        + ", new pkgSetting = " + newPkgSetting
                        + ", old pkg = " + oldPkg
                        + ", new pkg = " + newPkg, new Exception());

                DomainVerificationPkgState newPkgState = new DomainVerificationPkgState(
                        pkgName, newDomainSetId, true, newStateMap, newUserStates);
                mAttachedPkgStates.put(pkgName, newDomainSetId, newPkgState);
                return;
            }

            ArrayMap<String, Integer> oldStateMap = oldPkgState.getStateMap();
            ArraySet<String> newAutoVerifyDomains = mCollector.collectAutoVerifyDomains(newPkg);
            int newDomainsSize = newAutoVerifyDomains.size();

            for (int newDomainsIndex = 0; newDomainsIndex < newDomainsSize; newDomainsIndex++) {
                String domain = newAutoVerifyDomains.valueAt(newDomainsIndex);
                Integer oldStateInteger = oldStateMap.get(domain);
                if (oldStateInteger != null) {
                    int oldState = oldStateInteger;
                    switch (oldState) {
                        case DomainVerificationState.STATE_SUCCESS:
                        case DomainVerificationState.STATE_RESTORED:
                        case DomainVerificationState.STATE_MIGRATED:
                            newStateMap.put(domain, oldState);
                            break;
                        default:
                            // In all other cases, the state code is left unset
                            // (STATE_NO_RESPONSE) to signal to the verification agent that any
                            // existing error has been cleared and the domain should be
                            // re-attempted. This makes update of a package a signal to
                            // re-verify.
                            break;
                    }
                }
            }

            SparseArray<DomainVerificationUserState> oldUserStates =
                    oldPkgState.getUserSelectionStates();
            int oldUserStatesSize = oldUserStates.size();
            if (oldUserStatesSize > 0) {
                ArraySet<String> newWebDomains = mCollector.collectAutoVerifyDomains(newPkg);
                for (int oldUserStatesIndex = 0; oldUserStatesIndex < oldUserStatesSize;
                        oldUserStatesIndex++) {
                    int userId = oldUserStates.keyAt(oldUserStatesIndex);
                    DomainVerificationUserState oldUserState = oldUserStates.valueAt(
                            oldUserStatesIndex);
                    ArraySet<String> oldEnabledHosts = oldUserState.getEnabledHosts();
                    ArraySet<String> newEnabledHosts = new ArraySet<>(oldEnabledHosts);
                    newEnabledHosts.retainAll(newWebDomains);
                    DomainVerificationUserState newUserState = new DomainVerificationUserState(
                            userId, newEnabledHosts, oldUserState.isDisallowLinkHandling());
                    newUserStates.put(userId, newUserState);
                }
            }

            boolean hasAutoVerifyDomains = newDomainsSize > 0;
            boolean stateApplied = applyImmutableState(pkgName, newStateMap, newAutoVerifyDomains);

            // TODO(b/159952358): sendBroadcast should be abstracted so it doesn't have to be aware
            //  of whether/what state was applied. Probably some method which iterates the map to
            //  check for any domains that actually have state changeable by the domain verification
            //  agent.
            sendBroadcast = hasAutoVerifyDomains && !stateApplied;

            mAttachedPkgStates.put(pkgName, newDomainSetId, new DomainVerificationPkgState(
                    pkgName, newDomainSetId, hasAutoVerifyDomains, newStateMap, newUserStates));
        }

        if (sendBroadcast) {
            sendBroadcastForPackage(pkgName);
        }
    }

    // TODO(b/159952358): Handle valid domainSetIds for PackageSettings with no AndroidPackage
    @Override
    public void addPackage(@NonNull PackageSetting newPkgSetting) {
        // TODO(b/159952358): Optimize packages without any domains. Those wouldn't have to be in
        //  the state map, but it would require handling the "migration" case where an app either
        //  gains or loses all domains.

        UUID domainSetId = newPkgSetting.getDomainSetId();
        String pkgName = newPkgSetting.name;

        boolean sendBroadcast = true;

        DomainVerificationPkgState pkgState;
        pkgState = mSettings.getPendingState(pkgName);
        if (pkgState != null) {
            // Don't send when attaching from pending read, which is usually boot scan. Re-send on
            // boot is handled in a separate method once all packages are added.
            sendBroadcast = false;
        } else {
            pkgState = mSettings.getRestoredState(pkgName);
        }

        AndroidPackage pkg = newPkgSetting.getPkg();
        ArraySet<String> domains = mCollector.collectAutoVerifyDomains(pkg);
        boolean hasAutoVerifyDomains = !domains.isEmpty();
        boolean isPendingOrRestored = pkgState != null;
        if (isPendingOrRestored) {
            pkgState.setId(domainSetId);
        } else {
            pkgState = new DomainVerificationPkgState(pkgName, domainSetId, hasAutoVerifyDomains);
        }

        boolean stateApplied = applyImmutableState(pkgState, domains);
        if (!stateApplied && !isPendingOrRestored) {
            // TODO(b/159952358): Test this behavior
            // Attempt to preserve user experience by automatically verifying all domains from
            // legacy state if they were previously approved, or by automatically enabling all
            // hosts through user selection if legacy state indicates a user previously made the
            // choice in settings to allow supported links. The domain verification agent should
            // re-verify these links (set to STATE_MIGRATED) at the next possible opportunity,
            // and disable them if appropriate.
            ArraySet<String> webDomains = null;

            @SuppressWarnings("deprecation")
            SparseArray<PackageUserState> userState = newPkgSetting.getUserState();
            int userStateSize = userState.size();
            for (int index = 0; index < userStateSize; index++) {
                int userId = userState.keyAt(index);
                int legacyStatus = userState.valueAt(index).domainVerificationStatus;
                if (legacyStatus
                        == PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) {
                    if (webDomains == null) {
                        webDomains = mCollector.collectAllWebDomains(pkg);
                    }

                    pkgState.getOrCreateUserSelectionState(userId).addHosts(webDomains);
                }
            }

            IntentFilterVerificationInfo legacyInfo =
                    newPkgSetting.getIntentFilterVerificationInfo();
            if (legacyInfo != null
                    && legacyInfo.getStatus()
                    == PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) {
                ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
                int domainsSize = domains.size();
                for (int index = 0; index < domainsSize; index++) {
                    stateMap.put(domains.valueAt(index), DomainVerificationState.STATE_MIGRATED);
                }
            }
        }

        synchronized (mLock) {
            mAttachedPkgStates.put(pkgName, domainSetId, pkgState);
        }

        if (sendBroadcast && hasAutoVerifyDomains) {
            sendBroadcastForPackage(pkgName);
        }
    }

    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.
     */
    private boolean applyImmutableState(@NonNull String packageName,
            @NonNull ArrayMap<String, Integer> stateMap,
            @NonNull ArraySet<String> autoVerifyDomains) {
        if (mSystemConfig.getLinkedApps().contains(packageName)) {
            int domainsSize = autoVerifyDomains.size();
            for (int index = 0; index < domainsSize; index++) {
                stateMap.put(autoVerifyDomains.valueAt(index),
                        DomainVerificationState.STATE_APPROVED);
            }
            return true;
        }

        return false;
    }

    @Override
    public void writeSettings(@NonNull TypedXmlSerializer serializer) throws IOException {
        synchronized (mLock) {
@@ -195,6 +417,10 @@ public class DomainVerificationService extends SystemService
        mConnection.get().scheduleWriteSettings();
    }

    private void sendBroadcastForPackage(@NonNull String packageName) {
        // TODO(b/159952358): Implement proxy
    }

    public interface Connection {

        /**
+15 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.server.pm.domain.verify;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.pm.domain.verify.DomainVerificationState;
import android.os.UserHandle;
@@ -253,4 +254,18 @@ class DomainVerificationSettings {
            mRestoredPkgStates.valueAt(index).removeUser(userId);
        }
    }

    @Nullable
    public DomainVerificationPkgState getPendingState(@NonNull String pkgName) {
        synchronized (mLock) {
            return mPendingPkgStates.get(pkgName);
        }
    }

    @Nullable
    public DomainVerificationPkgState getRestoredState(@NonNull String pkgName) {
        synchronized (mLock) {
            return mRestoredPkgStates.get(pkgName);
        }
    }
}
Loading