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

Commit 6821adee authored by Winson's avatar Winson
Browse files

Fix DomainVerificationEnforcer for app visibility

Cleans up the permission logic and introduces app filtering.

This will require non-user targeted APIs that hit a package name
to hold QUERY_ALL_PACKAGES. This ensures the caller can see the packages
without needing to check instant app state by passing a userId.

Currently it's not clear how to handle instant app visibility, but it's
assumed that the verification agent and settings have visibility.

Bug: 171251883

Test: atest DomainVerificationEnforcerTest

Change-Id: I3ffe2cc0307c9efa97dfcf8474a620622e7cfcfe
parent 1afc2cdb
Loading
Loading
Loading
Loading
+6 −2
Original line number Diff line number Diff line
@@ -1747,6 +1747,11 @@ public class PackageManagerService extends IPackageManager.Stub
        public AndroidPackage getPackage(@NonNull String packageName) {
            return getPackageLocked(packageName);
        }
        @Override
        public boolean filterAppAccess(String packageName, int callingUid, int userId) {
            return mPmInternal.filterAppAccess(packageName, callingUid, userId);
        }
    }
    /**
@@ -16156,8 +16161,7 @@ public class PackageManagerService extends IPackageManager.Stub
    @Deprecated
    @Override
    public boolean updateIntentVerificationStatus(String packageName, int status, int userId) {
        mDomainVerificationManager.setLegacyUserState(packageName, userId, status);
        return true;
        return mDomainVerificationManager.setLegacyUserState(packageName, userId, status);
    }
    @Deprecated
+3 −4
Original line number Diff line number Diff line
@@ -105,9 +105,6 @@ 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.verify.domain.DomainVerificationLegacySettings;
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
import com.android.server.pm.verify.domain.DomainVerificationPersistence;
import com.android.server.pm.parsing.PackageInfoUtils;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
@@ -115,6 +112,9 @@ import com.android.server.pm.permission.LegacyPermissionDataProvider;
import com.android.server.pm.permission.LegacyPermissionSettings;
import com.android.server.pm.permission.LegacyPermissionState;
import com.android.server.pm.permission.LegacyPermissionState.PermissionState;
import com.android.server.pm.verify.domain.DomainVerificationLegacySettings;
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
import com.android.server.pm.verify.domain.DomainVerificationPersistence;
import com.android.server.utils.Snappable;
import com.android.server.utils.TimingsTraceAndSlog;
import com.android.server.utils.Watchable;
@@ -2707,7 +2707,6 @@ public final class Settings implements Watchable, Snappable {
        writeSigningKeySetLPr(serializer, pkg.keySetData);
        writeUpgradeKeySetsLPr(serializer, pkg.keySetData);
        writeKeySetAliasesLPr(serializer, pkg.keySetData);
        mDomainVerificationManager.writeLegacySettings(serializer, pkg.name);
        writeMimeGroupLPr(serializer, pkg.mimeGroups);

        serializer.endTag(null, "package");
+78 −11
Original line number Diff line number Diff line
@@ -18,8 +18,10 @@ package com.android.server.pm.verify.domain;

import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Process;

@@ -30,10 +32,17 @@ public class DomainVerificationEnforcer {
    @NonNull
    private final Context mContext;

    @NonNull
    private Callback mCallback;

    public DomainVerificationEnforcer(@NonNull Context context) {
        mContext = context;
    }

    public void setCallback(@NonNull Callback callback) {
        mCallback = callback;
    }

    /**
     * Enforced when mutating any state from shell or internally in the system process.
     */
@@ -67,6 +76,11 @@ public class DomainVerificationEnforcer {
                            "Caller " + callingUid
                                    + " is not allowed to query domain verification state");
                }

                mContext.enforcePermission(android.Manifest.permission.QUERY_ALL_PACKAGES,
                        Binder.getCallingPid(), callingUid,
                        "Caller " + callingUid + " does not hold "
                                + android.Manifest.permission.QUERY_ALL_PACKAGES);
                break;
        }
    }
@@ -84,28 +98,42 @@ public class DomainVerificationEnforcer {
                isAllowed = true;
                break;
            default:
                // TODO(b/159952358): Remove permission check? The component package should
                //  have been checked when the verifier component was first scanned in PMS.
                mContext.enforcePermission(
                        android.Manifest.permission.DOMAIN_VERIFICATION_AGENT,
                        Binder.getCallingPid(), callingUid,
                        "Caller " + callingUid + " does not hold DOMAIN_VERIFICATION_AGENT");
                final int callingPid = Binder.getCallingPid();
                boolean isLegacyVerificationAgent = false;
                if (mContext.checkPermission(
                        android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, callingPid,
                        callingUid) != PackageManager.PERMISSION_GRANTED) {
                    isLegacyVerificationAgent = mContext.checkPermission(
                            android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT,
                            callingPid, callingUid) == PackageManager.PERMISSION_GRANTED;
                    if (!isLegacyVerificationAgent) {
                        throw new SecurityException("Caller " + callingUid + " does not hold "
                                + android.Manifest.permission.DOMAIN_VERIFICATION_AGENT);
                    }
                }

                // If the caller isn't a legacy verifier, it needs the QUERY_ALL permission
                if (!isLegacyVerificationAgent) {
                    mContext.enforcePermission(android.Manifest.permission.QUERY_ALL_PACKAGES,
                            callingPid, callingUid, "Caller " + callingUid + " does not hold "
                                    + android.Manifest.permission.QUERY_ALL_PACKAGES);
                }

                isAllowed = proxy.isCallerVerifier(callingUid);
                break;
        }

        if (!isAllowed) {
            throw new SecurityException("Caller " + callingUid
                    + " is not the approved domain verification agent, isVerifier = "
                    + proxy.isCallerVerifier(callingUid));
                    + " is not the approved domain verification agent");
        }
    }

    /**
     * Enforced when mutating user selection state inside an exposed API method.
     */
    public void assertApprovedUserSelector(int callingUid, @UserIdInt int callingUserId,
            @UserIdInt int targetUserId) throws SecurityException {
    public boolean assertApprovedUserSelector(int callingUid, @UserIdInt int callingUserId,
            @Nullable String packageName, @UserIdInt int targetUserId) throws SecurityException {
        if (callingUserId != targetUserId) {
            mContext.enforcePermission(
                    Manifest.permission.INTERACT_ACROSS_USERS,
@@ -117,12 +145,51 @@ public class DomainVerificationEnforcer {
                android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION,
                Binder.getCallingPid(), callingUid,
                "Caller is not allowed to edit user selections");

        if (packageName == null) {
            return true;
        }

        return !mCallback.filterAppAccess(packageName, callingUid, targetUserId);
    }

    public void callerIsLegacyUserSelector(int callingUid) {
    public boolean callerIsLegacyUserSelector(int callingUid, @UserIdInt int callingUserId,
            @NonNull String packageName, @UserIdInt int targetUserId) {
        mContext.enforcePermission(
                android.Manifest.permission.SET_PREFERRED_APPLICATIONS,
                Binder.getCallingPid(), callingUid,
                "Caller is not allowed to edit user state");

        if (callingUserId != targetUserId) {
            if (mContext.checkPermission(
                    Manifest.permission.INTERACT_ACROSS_USERS,
                    Binder.getCallingPid(), callingUid) != PackageManager.PERMISSION_GRANTED) {
                // Legacy API did not enforce this, so for backwards compatibility, fail silently
                return false;
            }
        }

        return !mCallback.filterAppAccess(packageName, callingUid, targetUserId);
    }

    public boolean callerIsLegacyUserQuerent(int callingUid, @UserIdInt int callingUserId,
            @NonNull String packageName, @UserIdInt int targetUserId) {
        if (callingUserId != targetUserId) {
            // The legacy API enforces the _FULL variant, so maintain that here
            mContext.enforcePermission(
                    Manifest.permission.INTERACT_ACROSS_USERS_FULL,
                    Binder.getCallingPid(), callingUid,
                    "Caller is not allowed to edit other users");
        }

        return !mCallback.filterAppAccess(packageName, callingUid, targetUserId);
    }

    public interface Callback {
        /**
         * @return true if access to the given package should be filtered and the method failed as
         * if the package was not installed
         */
        boolean filterAppAccess(@NonNull String packageName, int callingUid, @UserIdInt int userId);
    }
}
+5 −8
Original line number Diff line number Diff line
@@ -174,8 +174,10 @@ public interface DomainVerificationManagerInternal extends DomainVerificationMan
     * Set aside a legacy user selection that will be restored to a pending
     * {@link DomainVerificationPkgState} once it's added through
     * {@link #addPackage(PackageSetting)}.
     *
     * @return true if state changed successfully
     */
    void setLegacyUserState(@NonNull String packageName, @UserIdInt int userId, int state);
    boolean setLegacyUserState(@NonNull String packageName, @UserIdInt int userId, int state);

    /**
     * Until the legacy APIs are entirely removed, returns the legacy state from the previously
@@ -183,12 +185,6 @@ public interface DomainVerificationManagerInternal extends DomainVerificationMan
     */
    int getLegacyState(@NonNull String packageName, @UserIdInt int userId);

    /**
     * Serialize a legacy setting that wasn't attached yet.
     * TODO: Does this even matter? Should consider for removal.
     */
    void writeLegacySettings(TypedXmlSerializer serializer, String name);

    /**
     * Print the verification state and user selection state of a package.
     *
@@ -235,7 +231,8 @@ public interface DomainVerificationManagerInternal extends DomainVerificationMan
            throws IllegalArgumentException, NameNotFoundException;


    interface Connection extends Function<String, PackageSetting> {
    interface Connection extends DomainVerificationEnforcer.Callback,
            Function<String, PackageSetting> {

        /**
         * Notify that a settings change has been made and that eventually
+46 −19
Original line number Diff line number Diff line
@@ -47,12 +47,12 @@ 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.parsing.pkg.AndroidPackage;
import com.android.server.pm.verify.domain.models.DomainVerificationPkgState;
import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;
import com.android.server.pm.verify.domain.models.DomainVerificationUserState;
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyUnavailable;
import com.android.server.pm.parsing.pkg.AndroidPackage;

import org.xmlpull.v1.XmlPullParserException;

@@ -92,9 +92,9 @@ public class DomainVerificationService extends SystemService
     * immediately attached once its available.
     * <p>
     * Generally this should be not accessed directly. Prefer calling {@link
     * #getAndValidateAttachedLocked(UUID, Set, boolean)}.
     * #getAndValidateAttachedLocked(UUID, Set, boolean, int, Integer)}.
     *
     * @see #getAndValidateAttachedLocked(UUID, Set, boolean)
     * @see #getAndValidateAttachedLocked(UUID, Set, boolean, int, Integer)
     **/
    @GuardedBy("mLock")
    @NonNull
@@ -160,6 +160,7 @@ public class DomainVerificationService extends SystemService
    @Override
    public void setConnection(@NonNull Connection connection) {
        mConnection = connection;
        mEnforcer.setCallback(mConnection);
    }

    @NonNull
@@ -285,7 +286,7 @@ public class DomainVerificationService extends SystemService
        mEnforcer.assertApprovedVerifier(callingUid, mProxy);
        synchronized (mLock) {
            DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains,
                    true /* forAutoVerify */);
                    true /* forAutoVerify */, callingUid, null /* userId */);
            ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
            for (String domain : domains) {
                Integer previousState = stateMap.get(domain);
@@ -389,8 +390,10 @@ public class DomainVerificationService extends SystemService

    public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName,
            boolean allowed, @UserIdInt int userId) throws NameNotFoundException {
        mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(),
                mConnection.getCallingUserId(), userId);
        if (!mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(),
                mConnection.getCallingUserId(), packageName, userId)) {
            throw DomainVerificationUtils.throwPackageUnavailable(packageName);
        }
        synchronized (mLock) {
            DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
            if (pkgState == null) {
@@ -455,11 +458,18 @@ public class DomainVerificationService extends SystemService
    public void setDomainVerificationUserSelection(@NonNull UUID domainSetId,
            @NonNull Set<String> domains, boolean enabled, @UserIdInt int userId)
            throws InvalidDomainSetException, NameNotFoundException {
        mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(),
                mConnection.getCallingUserId(), userId);
        synchronized (mLock) {
            final int callingUid = mConnection.getCallingUid();
            // Pass null for package name here and do the app visibility enforcement inside
            // getAndValidateAttachedLocked instead, since this has to fail with the same invalid
            // ID reason if the target app is invisible
            if (!mEnforcer.assertApprovedUserSelector(callingUid, mConnection.getCallingUserId(),
                    null /* packageName */, userId)) {
                throw new InvalidDomainSetException(domainSetId, null,
                        InvalidDomainSetException.REASON_ID_INVALID);
            }
            DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains,
                    false /* forAutoVerify */);
                    false /* forAutoVerify */, callingUid, userId);
            DomainVerificationUserState userState = pkgState.getOrCreateUserSelectionState(userId);
            if (enabled) {
                userState.addHosts(domains);
@@ -556,8 +566,10 @@ public class DomainVerificationService extends SystemService
    @Override
    public DomainVerificationUserSelection getDomainVerificationUserSelection(
            @NonNull String packageName, @UserIdInt int userId) throws NameNotFoundException {
        mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(),
                mConnection.getCallingUserId(), userId);
        if (!mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(),
                mConnection.getCallingUserId(), packageName, userId)) {
            throw DomainVerificationUtils.throwPackageUnavailable(packageName);
        }
        synchronized (mLock) {
            DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
            if (pkgState == null) {
@@ -844,20 +856,24 @@ public class DomainVerificationService extends SystemService
    }

    @Override
    public void setLegacyUserState(@NonNull String packageName, @UserIdInt int userId, int state) {
        mEnforcer.callerIsLegacyUserSelector(mConnection.getCallingUid());
    public boolean setLegacyUserState(@NonNull String packageName, @UserIdInt int userId,
            int state) {
        if (!mEnforcer.callerIsLegacyUserSelector(mConnection.getCallingUid(),
                mConnection.getCallingUserId(), packageName, userId)) {
            return false;
        }
        mLegacySettings.add(packageName, userId, state);
        mConnection.scheduleWriteSettings();
        return true;
    }

    @Override
    public int getLegacyState(@NonNull String packageName, @UserIdInt int userId) {
        return mLegacySettings.getUserState(packageName, userId);
        if (!mEnforcer.callerIsLegacyUserQuerent(mConnection.getCallingUid(),
                mConnection.getCallingUserId(), packageName, userId)) {
            return PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
        }

    @Override
    public void writeLegacySettings(TypedXmlSerializer serializer, String name) {

        return mLegacySettings.getUserState(packageName, userId);
    }

    @Override
@@ -935,10 +951,14 @@ public class DomainVerificationService extends SystemService
     * Validates parameters provided by an external caller. Checks that an ID is still live and that
     * any provided domains are valid. Should be called at the beginning of each API that takes in a
     * {@link UUID} domain set ID.
     *
     * @param userIdForFilter which user to filter app access to, or null if the caller has already
     *                        validated package visibility
     */
    @GuardedBy("mLock")
    private DomainVerificationPkgState getAndValidateAttachedLocked(@NonNull UUID domainSetId,
            @NonNull Set<String> domains, boolean forAutoVerify)
            @NonNull Set<String> domains, boolean forAutoVerify, int callingUid,
            @Nullable Integer userIdForFilter)
            throws InvalidDomainSetException, NameNotFoundException {
        if (domainSetId == null) {
            throw new InvalidDomainSetException(null, null,
@@ -952,6 +972,13 @@ public class DomainVerificationService extends SystemService
        }

        String pkgName = pkgState.getPackageName();

        if (userIdForFilter != null
                && mConnection.filterAppAccess(pkgName, callingUid, userIdForFilter)) {
            throw new InvalidDomainSetException(domainSetId, null,
                    InvalidDomainSetException.REASON_ID_INVALID);
        }

        PackageSetting pkgSetting = mConnection.getPackageSettingLocked(pkgName);
        if (pkgSetting == null || pkgSetting.getPkg() == null) {
            throw DomainVerificationUtils.throwPackageUnavailable(pkgName);
Loading