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

Commit 1c042ba4 authored by Kevin Chyn's avatar Kevin Chyn
Browse files

linkToDeath on BiometricPrompt#authenticate

It's possible that the client dies. Currently the AuthSession doesn't
know about this, so it doesn't do the following. This CL adds them.

1) Close the SysUI dialog
2) Clean up any existing state with individual sensors

Test: atest com.android.server.biometrics
Test: 1) Use a face unlock device
      2) BiometricPromptDemo, start authentication
      3) Wait for auth to time out (try again button gets shown)
      4) Kill the calling app, e.g.
         `adb shell killall com.example.android.biometric`
      5) Start another activity to trigger task stack change, e.g.
         `adb shell am start -a ...`
      6) Start another authentication, e.g.
         `adb shell am start -a android.app.action.CONFIRM_DEVICE_CREDENTIAL`
      7) Authenticate, reject, or wait for timeout
Test: Repeat the above, but kill the app when the sensor is active
      (before auth times out in step 3)

Before: Upon step 6, no authentication UI is shown. Upon 7, SysUI would
        crash.

After: Upon step 6, a new authentication UI is shown. Upon 7, no crash
       observed, and auth behaves as expected.

Bug: 156719497

Change-Id: Ib573466edac7dd95a8c031e0533cbca693b806f6
Merged-In: Ib573466edac7dd95a8c031e0533cbca693b806f6
parent eb84f960
Loading
Loading
Loading
Loading
+2 −5
Original line number Diff line number Diff line
@@ -84,11 +84,8 @@ public abstract class AuthenticationClient extends ClientMonitor {

    @Override
    public void binderDied() {
        super.binderDied();
        // When the binder dies, we should stop the client. This probably belongs in
        // ClientMonitor's binderDied(), but testing all the cases would be tricky.
        // AuthenticationClient is the most user-visible case.
        stop(false /* initiatedByClient */);
        final boolean clearListener = !isBiometricPrompt();
        binderDiedInternal(clearListener);
    }

    @Override
+61 −3
Original line number Diff line number Diff line
@@ -113,6 +113,7 @@ public class BiometricService extends SystemService {
    private static final int MSG_ON_AUTHENTICATION_TIMED_OUT = 11;
    private static final int MSG_ON_DEVICE_CREDENTIAL_PRESSED = 12;
    private static final int MSG_ON_SYSTEM_EVENT = 13;
    private static final int MSG_CLIENT_DIED = 14;

    /**
     * Authentication either just called and we have not transitioned to the CALLED state, or
@@ -151,8 +152,13 @@ public class BiometricService extends SystemService {
     * Device credential in AuthController is showing
     */
    static final int STATE_SHOWING_DEVICE_CREDENTIAL = 8;
    /**
     * The client binder died, and sensors were authenticating at the time. Cancel has been
     * requested and we're waiting for the HAL(s) to send ERROR_CANCELED.
     */
    static final int STATE_CLIENT_DIED_CANCELLING = 9;

    final class AuthSession {
    final class AuthSession implements IBinder.DeathRecipient {
        // Map of Authenticator/Cookie pairs. We expect to receive the cookies back from
        // <Biometric>Services before we can start authenticating. Pairs that have been returned
        // are moved to mModalitiesMatched.
@@ -211,7 +217,14 @@ public class BiometricService extends SystemService {
            mCallingUserId = callingUserId;
            mModality = modality;
            mRequireConfirmation = requireConfirmation;

            Slog.d(TAG, "New AuthSession, mSysUiSessionId: " + mSysUiSessionId);

            try {
                mClientReceiver.asBinder().linkToDeath(this, 0 /* flags */);
            } catch (RemoteException e) {
                Slog.w(TAG, "Unable to link to death");
            }
        }

        boolean isCrypto() {
@@ -231,6 +244,12 @@ public class BiometricService extends SystemService {
        boolean isAllowDeviceCredential() {
            return Utils.isCredentialRequested(mBundle);
        }

        @Override
        public void binderDied() {
            Slog.e(TAG, "Binder died, sysUiSessionId: " + mSysUiSessionId);
            mHandler.obtainMessage(MSG_CLIENT_DIED).sendToTarget();
        }
    }

    private final Injector mInjector;
@@ -370,6 +389,11 @@ public class BiometricService extends SystemService {
                    break;
                }

                case MSG_CLIENT_DIED: {
                    handleClientDied();
                    break;
                }

                default:
                    Slog.e(TAG, "Unknown message: " + msg);
                    break;
@@ -1391,6 +1415,7 @@ public class BiometricService extends SystemService {
    }

    private void handleOnError(int cookie, int modality, int error, int vendorCode) {

        Slog.d(TAG, "handleOnError: " + error + " cookie: " + cookie);
        // Errors can either be from the current auth session or the pending auth session.
        // The pending auth session may receive errors such as ERROR_LOCKOUT before
@@ -1431,6 +1456,9 @@ public class BiometricService extends SystemService {
                } else if (mCurrentAuthSession.mState == STATE_SHOWING_DEVICE_CREDENTIAL) {
                    Slog.d(TAG, "Biometric canceled, ignoring from state: "
                            + mCurrentAuthSession.mState);
                } else if (mCurrentAuthSession.mState == STATE_CLIENT_DIED_CANCELLING) {
                    mStatusBarService.hideAuthenticationDialog();
                    mCurrentAuthSession = null;
                } else {
                    Slog.e(TAG, "Impossible session error state: "
                            + mCurrentAuthSession.mState);
@@ -1622,6 +1650,36 @@ public class BiometricService extends SystemService {
        }
    }

    private void handleClientDied() {
        if (mCurrentAuthSession == null) {
            Slog.e(TAG, "Auth session null");
            return;
        }

        Slog.e(TAG, "SysUiSessionId: " + mCurrentAuthSession.mSysUiSessionId
                + " State: " + mCurrentAuthSession.mState);

        try {
            // Check if any sensors are authenticating. If so, need to cancel them. When
            // ERROR_CANCELED is received from the HAL, we hide the dialog and cleanup the session.
            if (mCurrentAuthSession.mState == STATE_AUTH_STARTED) {
                mCurrentAuthSession.mState = STATE_CLIENT_DIED_CANCELLING;
                cancelInternal(mCurrentAuthSession.mToken,
                        mCurrentAuthSession.mOpPackageName,
                        mCurrentAuthSession.mCallingUid,
                        mCurrentAuthSession.mCallingPid,
                        mCurrentAuthSession.mCallingUserId,
                        false /* fromClient */);
            } else {
                // If the sensors are not authenticating, set the auth session to null.
                mStatusBarService.hideAuthenticationDialog();
                mCurrentAuthSession = null;
            }
        } catch (RemoteException e) {
            Slog.e(TAG, "Remote exception: " + e);
        }
    }

    /**
     * Invoked when each service has notified that its client is ready to be started. When
     * all biometrics are ready, this invokes the SystemUI dialog through StatusBar.
@@ -1822,11 +1880,11 @@ public class BiometricService extends SystemService {

    void cancelInternal(IBinder token, String opPackageName, int callingUid, int callingPid,
            int callingUserId, boolean fromClient) {

        if (mCurrentAuthSession == null) {
            Slog.w(TAG, "Skipping cancelInternal");
            return;
        } else if (mCurrentAuthSession.mState != STATE_AUTH_STARTED) {
        } else if (mCurrentAuthSession.mState != STATE_AUTH_STARTED
                && mCurrentAuthSession.mState != STATE_CLIENT_DIED_CANCELLING) {
            Slog.w(TAG, "Skipping cancelInternal, state: " + mCurrentAuthSession.mState);
            return;
        }
+7 −1
Original line number Diff line number Diff line
@@ -233,12 +233,18 @@ public abstract class ClientMonitor extends LoggableMonitor implements IBinder.D

    @Override
    public void binderDied() {
        binderDiedInternal(true /* clearListener */);
    }

    void binderDiedInternal(boolean clearListener) {
        // If the current client dies we should cancel the current operation.
        Slog.e(getLogTag(), "Binder died, cancelling client");
        stop(false /* initiatedByClient */);
        mToken = null;
        if (clearListener) {
            mListener = null;
        }
    }

    @Override
    protected void finalize() throws Throwable {
+79 −6
Original line number Diff line number Diff line
@@ -115,6 +115,8 @@ public class BiometricServiceTest {
    public void setUp() {
        MockitoAnnotations.initMocks(this);

        resetReceivers();

        when(mContext.getContentResolver()).thenReturn(mContentResolver);
        when(mContext.getResources()).thenReturn(mResources);
        when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE))
@@ -146,6 +148,74 @@ public class BiometricServiceTest {
        when(mInjector.getConfiguration(any())).thenReturn(config);
    }

    @Test
    public void testClientBinderDied_whenPaused() throws Exception {
        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);

        invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                true /* requireConfirmation */, null /* authenticators */);
        waitForIdle();
        verify(mReceiver1.asBinder()).linkToDeath(eq(mBiometricService.mCurrentAuthSession),
                anyInt());

        mBiometricService.mInternalReceiver.onError(
                getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
                BiometricAuthenticator.TYPE_FACE,
                BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
                0 /* vendorCode */);
        waitForIdle();

        assertEquals(BiometricService.STATE_AUTH_PAUSED,
                mBiometricService.mCurrentAuthSession.mState);

        mBiometricService.mCurrentAuthSession.binderDied();
        waitForIdle();

        assertNull(mBiometricService.mCurrentAuthSession);
        verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
        verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
    }

    @Test
    public void testClientBinderDied_whenAuthenticating() throws Exception {
        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);

        invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                true /* requireConfirmation */, null /* authenticators */);
        waitForIdle();
        verify(mReceiver1.asBinder()).linkToDeath(eq(mBiometricService.mCurrentAuthSession),
                anyInt());

        assertEquals(BiometricService.STATE_AUTH_STARTED,
                mBiometricService.mCurrentAuthSession.mState);
        mBiometricService.mCurrentAuthSession.binderDied();
        waitForIdle();

        assertNotNull(mBiometricService.mCurrentAuthSession);
        verify(mBiometricService.mStatusBarService, never()).hideAuthenticationDialog();
        assertEquals(BiometricService.STATE_CLIENT_DIED_CANCELLING,
                mBiometricService.mCurrentAuthSession.mState);

        verify(mBiometricService.mAuthenticators.get(0).impl).cancelAuthenticationFromService(
                any(),
                any(),
                anyInt(),
                anyInt(),
                anyInt(),
                eq(false) /* fromClient */);

        // Simulate ERROR_CANCELED received from HAL
        mBiometricService.mInternalReceiver.onError(
                getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
                BiometricAuthenticator.TYPE_FACE,
                BiometricConstants.BIOMETRIC_ERROR_CANCELED,
                0 /* vendorCode */);
        waitForIdle();
        verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
        verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
        assertNull(mBiometricService.mCurrentAuthSession);
    }

    @Test
    public void testAuthenticate_credentialAllowedButNotSetup_returnsNoDeviceCredential()
            throws Exception {
@@ -311,7 +381,7 @@ public class BiometricServiceTest {
                eq(0 /* vendorCode */));

        // Enrolled, not disabled in settings, user requires confirmation in settings
        resetReceiver();
        resetReceivers();
        when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(true);
        when(mBiometricService.mSettingObserver.getFaceAlwaysRequireConfirmation(anyInt()))
                .thenReturn(true);
@@ -332,7 +402,7 @@ public class BiometricServiceTest {
                anyInt() /* callingUserId */);

        // Enrolled, not disabled in settings, user doesn't require confirmation in settings
        resetReceiver();
        resetReceivers();
        when(mBiometricService.mSettingObserver.getFaceAlwaysRequireConfirmation(anyInt()))
                .thenReturn(false);
        invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
@@ -1198,7 +1268,7 @@ public class BiometricServiceTest {
                eq(0) /* vendorCode */);

        // Request for weak auth works
        resetReceiver();
        resetReceivers();
        authenticators = Authenticators.BIOMETRIC_WEAK;
        assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
                invokeCanAuthenticate(mBiometricService, authenticators));
@@ -1217,7 +1287,7 @@ public class BiometricServiceTest {
                anyInt() /* sysUiSessionId */);

        // Requesting strong and credential, when credential is setup
        resetReceiver();
        resetReceivers();
        authenticators = Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL;
        when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true);
        assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
@@ -1244,7 +1314,7 @@ public class BiometricServiceTest {
            }
        }

        resetReceiver();
        resetReceivers();
        authenticators = Authenticators.BIOMETRIC_STRONG;
        assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
                invokeCanAuthenticate(mBiometricService, authenticators));
@@ -1449,9 +1519,12 @@ public class BiometricServiceTest {
        }
    }

    private void resetReceiver() {
    private void resetReceivers() {
        mReceiver1 = mock(IBiometricServiceReceiver.class);
        mReceiver2 = mock(IBiometricServiceReceiver.class);

        when(mReceiver1.asBinder()).thenReturn(mock(Binder.class));
        when(mReceiver2.asBinder()).thenReturn(mock(Binder.class));
    }

    private void resetStatusBar() {