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

Commit 14b81ed6 authored by Song Chun Fan's avatar Song Chun Fan
Browse files

[ADI][14/N] enable verifier on multi-user

We cannot guarantee that the verifier service has singleUser=true. If
the service is not intended to be singleUser, we must make sure that the
service on one user doesn't affect the service on another.

FLAG: android.content.pm.verification_service
BUG:  360129657
Test: more tests to be added
Test: manually with the demo verifier app with user switching

Merged-In: I855fa867f04af4e979f5bc8b7d45373b6d424be9
Change-Id: I855fa867f04af4e979f5bc8b7d45373b6d424be9
parent 1671605c
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -1371,7 +1371,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            // Start binding to the verification service, if not bound already.
            mVerifierController.bindToVerifierServiceIfNeeded(mPm::snapshotComputer, userId);
            if (!TextUtils.isEmpty(params.appPackageName)) {
                mVerifierController.notifyPackageNameAvailable(params.appPackageName);
                mVerifierController.notifyPackageNameAvailable(params.appPackageName, userId);
            }
        }
    }
@@ -3209,7 +3209,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
         */
        public void onTimeout() {
            // Always notify the verifier, regardless of the policy.
            mVerifierController.notifyVerificationTimeout(sessionId);
            mVerifierController.notifyVerificationTimeout(sessionId, userId);
            mHandler.post(() -> {
                if (mCurrentVerificationPolicy.get() != VERIFICATION_POLICY_BLOCK_FAIL_CLOSED) {
                    // Continue with the rest of the verification and installation.
@@ -6035,7 +6035,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            // Only notify for the cancellation if the verification request has not
            // been sent out, which happens right after commit() is called.
            mVerifierController.notifyVerificationCancelled(
                    params.appPackageName);
                    params.appPackageName, userId);
        }
    }

+106 −59
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ import android.net.Uri;
import android.os.Handler;
import android.os.PersistableBundle;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.Slog;
import android.util.SparseArray;
@@ -112,16 +113,18 @@ public class VerifierController {
    // Guards the remote service object, as well as the verifier name and UID, which should all be
    // changed at the same time.
    private final Object mLock = new Object();
    @Nullable
    @GuardedBy("mLock")
    private ServiceConnector<IVerifierService> mRemoteService;
    // Map of userId -> remote verifier service for the user.
    @NonNull
    @GuardedBy("mLock")
    private int mRemoteServiceUid = INVALID_UID;
    private final SparseArray<ServiceConnectorWrapper> mRemoteServices = new SparseArray<>();

    @NonNull
    private final Injector mInjector;

    // The package name of default verifier, as specified by the system. Null if the system does not
    // specify a default verifier.
    @Nullable
    private final String mVerifierPackageName;
    private final String mDefaultVerifierPackageName;

    // Repository of active verification sessions and their status, mapping from id to status.
    @NonNull
@@ -142,16 +145,17 @@ public class VerifierController {

    @VisibleForTesting
    public VerifierController(@NonNull Context context, @NonNull Handler handler,
            @Nullable String verifierPackageName, @NonNull Injector injector) {
            @Nullable String defaultVerifierPackageName, @NonNull Injector injector) {
        mContext = context;
        mHandler = handler;
        mVerifierPackageName = verifierPackageName;
        mDefaultVerifierPackageName = defaultVerifierPackageName;
        mInjector = injector;
    }

    /**
     * Used by the installation session to get the package name of the installed verifier.
     * It can be overwritten by a system config for testing purpose.
     * Note: there can be only one verifier specified for all users on device.
     * TODO(b/360129657): remove debug property and verifier override after moving tests to cts-root
     */
    @Nullable
@@ -161,7 +165,7 @@ public class VerifierController {
        if (!verifierPackageOverride.isEmpty() && isValidPackageName(verifierPackageOverride)) {
            return verifierPackageOverride;
        }
        return mVerifierPackageName;
        return mDefaultVerifierPackageName;
    }

    /**
@@ -182,20 +186,30 @@ public class VerifierController {
     */
    public boolean bindToVerifierServiceIfNeeded(Supplier<Computer> snapshotSupplier, int userId) {
        if (DEBUG) {
            Slog.i(TAG, "Requesting to bind to the verifier service.");
            Slog.i(TAG, "Requesting to bind to the verifier service for user " + userId);
        }
        if (mRemoteService != null) {
            // Already connected
        final String verifierPackageName = getVerifierPackageName();
        synchronized (mLock) {
            var remoteService = mRemoteServices.get(userId);
            if (remoteService != null) {
                // The system has already bound to a verifier. Check if it's still valid.
                if (!remoteService.getVerifierPackageName().equals(verifierPackageName)) {
                    // The verifier name has been overridden. Clear the connected remote service.
                    destroy(userId);
                } else {
                    // The requested verifier is still connected. Directly return.
                    if (DEBUG) {
                Slog.i(TAG, "Verifier service is already connected.");
                        Slog.i(TAG, "Verifier service is already connected for user " + userId);
                    }
                    return true;
                }
        final String verifierPackageName = getVerifierPackageName();
            }
        }
        if (verifierPackageName == null) {
            // The system has no verifier installed, and if it has not been overwritten by any tests
            // The system has no default verifier specified, and it has not been overridden either
            return false;
        }
        // Start establishing a new connection. First check if the verifier is installed.
        final int verifierUid = snapshotSupplier.get().getPackageUidInternal(
                verifierPackageName, 0, userId, /* callingUid= */ SYSTEM_UID);
        if (verifierUid == INVALID_UID) {
@@ -205,68 +219,70 @@ public class VerifierController {
            }
            return false;
        }
        synchronized (mLock) {
            mRemoteService = mInjector.getRemoteService(
        final var remoteService = mInjector.getRemoteService(
                verifierPackageName, mContext, userId, mHandler);
            mRemoteServiceUid = verifierUid;
        }

        if (DEBUG) {
            Slog.i(TAG, "Connecting to a qualified verifier: " + verifierPackageName);
        }
        mRemoteService.setServiceLifecycleCallbacks(
        remoteService.setServiceLifecycleCallbacks(
                new ServiceConnector.ServiceLifecycleCallbacks<>() {
                    @Override
                    public void onConnected(@NonNull IVerifierService service) {
                        Slog.i(TAG, "Verifier " + verifierPackageName + " is connected");
                        Slog.i(TAG, "Verifier " + verifierPackageName + " is connected"
                                + " on user " + userId);
                    }

                    @Override
                    public void onDisconnected(@NonNull IVerifierService service) {
                        Slog.w(TAG,
                                "Verifier " + verifierPackageName + " is disconnected");
                        destroy();
                                "Verifier " + verifierPackageName + " is disconnected"
                                        + " on user " + userId);
                        destroy(userId);
                    }

                    @Override
                    public void onBinderDied() {
                        Slog.w(TAG, "Verifier " + verifierPackageName + " has died");
                        destroy();
                        Slog.w(TAG, "Verifier " + verifierPackageName + " has died"
                                + " on user " + userId);
                        destroy(userId);
                    }

                    private void destroy() {
                });
        synchronized (mLock) {
                            if (isVerifierConnectedLocked()) {
                                mRemoteService.unbind();
                                mRemoteService = null;
                                mRemoteServiceUid = INVALID_UID;
                            }
            mRemoteServices.put(userId, new ServiceConnectorWrapper(
                    remoteService, verifierUid, verifierPackageName));
        }
        if (DEBUG) {
            Slog.i(TAG, "Connecting to a qualified verifier: " + verifierPackageName
                    + " on user " + userId);
        }
                });
        AndroidFuture<IVerifierService> unusedFuture = mRemoteService.connect();
        AndroidFuture<IVerifierService> unusedFuture = remoteService.connect();
        return true;
    }

    @GuardedBy("mLock")
    private boolean isVerifierConnectedLocked() {
        return mRemoteService != null;
    private void destroy(int userId) {
        synchronized (mLock) {
            if (mRemoteServices.contains(userId)) {
                var remoteService = mRemoteServices.get(userId);
                if (remoteService != null) {
                    remoteService.getService().unbind();
                    mRemoteServices.remove(userId);
                }
            }
        }
    }

    /**
     * Called to notify the bound verifier agent that a package name is available and will soon be
     * requested for verification.
     */
    public void notifyPackageNameAvailable(@NonNull String packageName) {
    public void notifyPackageNameAvailable(@NonNull String packageName, int userId) {
        synchronized (mLock) {
            if (!isVerifierConnectedLocked()) {
            var remoteService = mRemoteServices.get(userId);
            if (remoteService == null) {
                if (DEBUG) {
                    Slog.i(TAG, "Verifier is not connected. Not notifying package name available");
                }
                return;
            }
            // Best effort. We don't check for the result.
            mRemoteService.run(service -> {
            remoteService.getService().run(service -> {
                if (DEBUG) {
                    Slog.i(TAG, "Notifying package name available for " + packageName);
                }
@@ -280,16 +296,17 @@ public class VerifierController {
     * {@link android.content.pm.verify.pkg.VerifierService#onPackageNameAvailable(String)}
     * will no longer be requested for verification, possibly because the installation is canceled.
     */
    public void notifyVerificationCancelled(@NonNull String packageName) {
    public void notifyVerificationCancelled(@NonNull String packageName, int userId) {
        synchronized (mLock) {
            if (!isVerifierConnectedLocked()) {
            var remoteService = mRemoteServices.get(userId);
            if (remoteService == null) {
                if (DEBUG) {
                    Slog.i(TAG, "Verifier is not connected. Not notifying verification cancelled");
                }
                return;
            }
            // Best effort. We don't check for the result.
            mRemoteService.run(service -> {
            remoteService.getService().run(service -> {
                if (DEBUG) {
                    Slog.i(TAG, "Notifying verification cancelled for " + packageName);
                }
@@ -325,7 +342,8 @@ public class VerifierController {
        // For now, the verification id is the same as the installation session id.
        final int verificationId = installationSessionId;
        synchronized (mLock) {
            if (!isVerifierConnectedLocked()) {
            var remoteService = mRemoteServices.get(userId);
            if (remoteService == null) {
                if (DEBUG) {
                    Slog.i(TAG, "Verifier is not connected. Not notifying verification required");
                }
@@ -339,7 +357,7 @@ public class VerifierController {
                    /* installSessionId= */ installationSessionId,
                    packageName, stagedPackageUri, signingInfo, declaredLibraries, extensionParams,
                    verificationPolicy, new VerificationSessionInterface(callback));
            AndroidFuture<Void> unusedFuture = mRemoteService.post(service -> {
            AndroidFuture<Void> unusedFuture = remoteService.getService().post(service -> {
                if (!retry) {
                    if (DEBUG) {
                        Slog.i(TAG, "Notifying verification required for session "
@@ -404,9 +422,10 @@ public class VerifierController {
    /**
     * Called to notify the bound verifier agent that a verification request has timed out.
     */
    public void notifyVerificationTimeout(int verificationId) {
    public void notifyVerificationTimeout(int verificationId, int userId) {
        synchronized (mLock) {
            if (!isVerifierConnectedLocked()) {
            var remoteService = mRemoteServices.get(userId);
            if (remoteService == null) {
                if (DEBUG) {
                    Slog.i(TAG,
                            "Verifier is not connected. Not notifying timeout for "
@@ -414,7 +433,7 @@ public class VerifierController {
                }
                return;
            }
            AndroidFuture<Void> unusedFuture = mRemoteService.post(service -> {
            AndroidFuture<Void> unusedFuture = remoteService.getService().post(service -> {
                if (DEBUG) {
                    Slog.i(TAG, "Notifying timeout for " + verificationId);
                }
@@ -448,12 +467,15 @@ public class VerifierController {
     * Assert that the calling UID is the same as the UID of the currently connected verifier.
     */
    public void assertCallerIsCurrentVerifier(int callingUid) {
        final int userId = UserHandle.getUserId(callingUid);
        synchronized (mLock) {
            if (!isVerifierConnectedLocked()) {
            var remoteService = mRemoteServices.get(userId);
            if (remoteService == null) {
                throw new IllegalStateException(
                        "Unable to proceed because the verifier has been disconnected.");
                        "Unable to proceed because the verifier has been disconnected"
                                + " for user " + userId);
            }
            if (callingUid != mRemoteServiceUid) {
            if (callingUid != remoteService.getUid()) {
                throw new IllegalStateException(
                        "Calling uid " + callingUid + " is not the current verifier.");
            }
@@ -543,6 +565,31 @@ public class VerifierController {
        }
    }

    private static class ServiceConnectorWrapper {
        // Remote service that receives verification requests
        private final @NonNull ServiceConnector<IVerifierService> mRemoteService;
        // UID of the remote service which includes the userId as part of it
        private final int mUid;
        // Package name of the verifier that was bound to. This can be different from the verifier
        // originally specified by the system.
        private final @NonNull String mVerifierPackageName;
        ServiceConnectorWrapper(@NonNull ServiceConnector<IVerifierService> service, int uid,
                @NonNull String verifierPackageName) {
            mRemoteService = service;
            mUid = uid;
            mVerifierPackageName = verifierPackageName;
        }
        ServiceConnector<IVerifierService> getService() {
            return mRemoteService;
        }
        int getUid() {
            return mUid;
        }
        @NonNull String getVerifierPackageName() {
            return mVerifierPackageName;
        }
    }

    @VisibleForTesting
    public static class Injector {
        /**
+10 −10
Original line number Diff line number Diff line
@@ -177,9 +177,9 @@ public class VerifierControllerTest {
        assertThat(mVerifierController.bindToVerifierServiceIfNeeded(mSnapshotSupplier, 0))
                .isFalse();
        // Test that nothing crashes if the verifier is available even though there's no bound
        mVerifierController.notifyPackageNameAvailable(TEST_PACKAGE_NAME);
        mVerifierController.notifyVerificationCancelled(TEST_PACKAGE_NAME);
        mVerifierController.notifyVerificationTimeout(-1);
        mVerifierController.notifyPackageNameAvailable(TEST_PACKAGE_NAME, 0);
        mVerifierController.notifyVerificationCancelled(TEST_PACKAGE_NAME, 0);
        mVerifierController.notifyVerificationTimeout(-1, 0);
        // Since there was no bound, no call is made to the verifier
        verifyNoMoreInteractions(mMockService);
    }
@@ -200,9 +200,9 @@ public class VerifierControllerTest {
        callbacks.onBinderDied();
        // Test that nothing crashes if the service connection is lost
        assertThat(mVerifierController.getVerifierPackageName()).isNotNull();
        mVerifierController.notifyPackageNameAvailable(TEST_PACKAGE_NAME);
        mVerifierController.notifyVerificationCancelled(TEST_PACKAGE_NAME);
        mVerifierController.notifyVerificationTimeout(TEST_ID);
        mVerifierController.notifyPackageNameAvailable(TEST_PACKAGE_NAME, 0);
        mVerifierController.notifyVerificationCancelled(TEST_PACKAGE_NAME, 0);
        mVerifierController.notifyVerificationTimeout(TEST_ID, 0);
        verifyNoMoreInteractions(mMockService);
        assertThat(mVerifierController.startVerificationSession(
                mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
@@ -212,7 +212,7 @@ public class VerifierControllerTest {
                mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
                TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams,
                mSessionCallback, /* retry= */ true)).isTrue();
        mVerifierController.notifyVerificationTimeout(TEST_ID);
        mVerifierController.notifyVerificationTimeout(TEST_ID, 0);
        verify(mMockService, times(1)).onVerificationTimeout(eq(TEST_ID));
    }

@@ -222,7 +222,7 @@ public class VerifierControllerTest {
                mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
                TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams,
                mSessionCallback, /* retry= */ false)).isTrue();
        mVerifierController.notifyPackageNameAvailable(TEST_PACKAGE_NAME);
        mVerifierController.notifyPackageNameAvailable(TEST_PACKAGE_NAME, 0);
        verify(mMockService).onPackageNameAvailable(eq(TEST_PACKAGE_NAME));
    }

@@ -232,7 +232,7 @@ public class VerifierControllerTest {
                mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
                TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams,
                mSessionCallback, /* retry= */ false)).isTrue();
        mVerifierController.notifyVerificationCancelled(TEST_PACKAGE_NAME);
        mVerifierController.notifyVerificationCancelled(TEST_PACKAGE_NAME, 0);
        verify(mMockService).onVerificationCancelled(eq(TEST_PACKAGE_NAME));
    }

@@ -298,7 +298,7 @@ public class VerifierControllerTest {
                mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
                TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams,
                mSessionCallback, /* retry= */ true)).isTrue();
        mVerifierController.notifyVerificationTimeout(TEST_ID);
        mVerifierController.notifyVerificationTimeout(TEST_ID, 0);
        verify(mMockService).onVerificationTimeout(eq(TEST_ID));
    }