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
This commit is contained in:
@@ -84,11 +84,8 @@ public abstract class AuthenticationClient extends ClientMonitor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void binderDied() {
|
public void binderDied() {
|
||||||
super.binderDied();
|
final boolean clearListener = !isBiometricPrompt();
|
||||||
// When the binder dies, we should stop the client. This probably belongs in
|
binderDiedInternal(clearListener);
|
||||||
// ClientMonitor's binderDied(), but testing all the cases would be tricky.
|
|
||||||
// AuthenticationClient is the most user-visible case.
|
|
||||||
stop(false /* initiatedByClient */);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -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_AUTHENTICATION_TIMED_OUT = 11;
|
||||||
private static final int MSG_ON_DEVICE_CREDENTIAL_PRESSED = 12;
|
private static final int MSG_ON_DEVICE_CREDENTIAL_PRESSED = 12;
|
||||||
private static final int MSG_ON_SYSTEM_EVENT = 13;
|
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
|
* 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
|
* Device credential in AuthController is showing
|
||||||
*/
|
*/
|
||||||
static final int STATE_SHOWING_DEVICE_CREDENTIAL = 8;
|
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
|
// 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
|
// <Biometric>Services before we can start authenticating. Pairs that have been returned
|
||||||
// are moved to mModalitiesMatched.
|
// are moved to mModalitiesMatched.
|
||||||
@@ -211,7 +217,14 @@ public class BiometricService extends SystemService {
|
|||||||
mCallingUserId = callingUserId;
|
mCallingUserId = callingUserId;
|
||||||
mModality = modality;
|
mModality = modality;
|
||||||
mRequireConfirmation = requireConfirmation;
|
mRequireConfirmation = requireConfirmation;
|
||||||
|
|
||||||
Slog.d(TAG, "New AuthSession, mSysUiSessionId: " + mSysUiSessionId);
|
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() {
|
boolean isCrypto() {
|
||||||
@@ -231,6 +244,12 @@ public class BiometricService extends SystemService {
|
|||||||
boolean isAllowDeviceCredential() {
|
boolean isAllowDeviceCredential() {
|
||||||
return Utils.isCredentialRequested(mBundle);
|
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;
|
private final Injector mInjector;
|
||||||
@@ -370,6 +389,11 @@ public class BiometricService extends SystemService {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case MSG_CLIENT_DIED: {
|
||||||
|
handleClientDied();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Slog.e(TAG, "Unknown message: " + msg);
|
Slog.e(TAG, "Unknown message: " + msg);
|
||||||
break;
|
break;
|
||||||
@@ -1391,6 +1415,7 @@ public class BiometricService extends SystemService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void handleOnError(int cookie, int modality, int error, int vendorCode) {
|
private void handleOnError(int cookie, int modality, int error, int vendorCode) {
|
||||||
|
|
||||||
Slog.d(TAG, "handleOnError: " + error + " cookie: " + cookie);
|
Slog.d(TAG, "handleOnError: " + error + " cookie: " + cookie);
|
||||||
// Errors can either be from the current auth session or the pending auth session.
|
// 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
|
// 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) {
|
} else if (mCurrentAuthSession.mState == STATE_SHOWING_DEVICE_CREDENTIAL) {
|
||||||
Slog.d(TAG, "Biometric canceled, ignoring from state: "
|
Slog.d(TAG, "Biometric canceled, ignoring from state: "
|
||||||
+ mCurrentAuthSession.mState);
|
+ mCurrentAuthSession.mState);
|
||||||
|
} else if (mCurrentAuthSession.mState == STATE_CLIENT_DIED_CANCELLING) {
|
||||||
|
mStatusBarService.hideAuthenticationDialog();
|
||||||
|
mCurrentAuthSession = null;
|
||||||
} else {
|
} else {
|
||||||
Slog.e(TAG, "Impossible session error state: "
|
Slog.e(TAG, "Impossible session error state: "
|
||||||
+ mCurrentAuthSession.mState);
|
+ 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
|
* 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.
|
* 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,
|
void cancelInternal(IBinder token, String opPackageName, int callingUid, int callingPid,
|
||||||
int callingUserId, boolean fromClient) {
|
int callingUserId, boolean fromClient) {
|
||||||
|
|
||||||
if (mCurrentAuthSession == null) {
|
if (mCurrentAuthSession == null) {
|
||||||
Slog.w(TAG, "Skipping cancelInternal");
|
Slog.w(TAG, "Skipping cancelInternal");
|
||||||
return;
|
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);
|
Slog.w(TAG, "Skipping cancelInternal, state: " + mCurrentAuthSession.mState);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -233,11 +233,17 @@ public abstract class ClientMonitor extends LoggableMonitor implements IBinder.D
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void binderDied() {
|
public void binderDied() {
|
||||||
|
binderDiedInternal(true /* clearListener */);
|
||||||
|
}
|
||||||
|
|
||||||
|
void binderDiedInternal(boolean clearListener) {
|
||||||
// If the current client dies we should cancel the current operation.
|
// If the current client dies we should cancel the current operation.
|
||||||
Slog.e(getLogTag(), "Binder died, cancelling client");
|
Slog.e(getLogTag(), "Binder died, cancelling client");
|
||||||
stop(false /* initiatedByClient */);
|
stop(false /* initiatedByClient */);
|
||||||
mToken = null;
|
mToken = null;
|
||||||
mListener = null;
|
if (clearListener) {
|
||||||
|
mListener = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -115,6 +115,8 @@ public class BiometricServiceTest {
|
|||||||
public void setUp() {
|
public void setUp() {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
|
|
||||||
|
resetReceivers();
|
||||||
|
|
||||||
when(mContext.getContentResolver()).thenReturn(mContentResolver);
|
when(mContext.getContentResolver()).thenReturn(mContentResolver);
|
||||||
when(mContext.getResources()).thenReturn(mResources);
|
when(mContext.getResources()).thenReturn(mResources);
|
||||||
when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE))
|
when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE))
|
||||||
@@ -146,6 +148,74 @@ public class BiometricServiceTest {
|
|||||||
when(mInjector.getConfiguration(any())).thenReturn(config);
|
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
|
@Test
|
||||||
public void testAuthenticate_credentialAllowedButNotSetup_returnsNoDeviceCredential()
|
public void testAuthenticate_credentialAllowedButNotSetup_returnsNoDeviceCredential()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
@@ -311,7 +381,7 @@ public class BiometricServiceTest {
|
|||||||
eq(0 /* vendorCode */));
|
eq(0 /* vendorCode */));
|
||||||
|
|
||||||
// Enrolled, not disabled in settings, user requires confirmation in settings
|
// Enrolled, not disabled in settings, user requires confirmation in settings
|
||||||
resetReceiver();
|
resetReceivers();
|
||||||
when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(true);
|
when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(true);
|
||||||
when(mBiometricService.mSettingObserver.getFaceAlwaysRequireConfirmation(anyInt()))
|
when(mBiometricService.mSettingObserver.getFaceAlwaysRequireConfirmation(anyInt()))
|
||||||
.thenReturn(true);
|
.thenReturn(true);
|
||||||
@@ -332,7 +402,7 @@ public class BiometricServiceTest {
|
|||||||
anyInt() /* callingUserId */);
|
anyInt() /* callingUserId */);
|
||||||
|
|
||||||
// Enrolled, not disabled in settings, user doesn't require confirmation in settings
|
// Enrolled, not disabled in settings, user doesn't require confirmation in settings
|
||||||
resetReceiver();
|
resetReceivers();
|
||||||
when(mBiometricService.mSettingObserver.getFaceAlwaysRequireConfirmation(anyInt()))
|
when(mBiometricService.mSettingObserver.getFaceAlwaysRequireConfirmation(anyInt()))
|
||||||
.thenReturn(false);
|
.thenReturn(false);
|
||||||
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
|
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
|
||||||
@@ -1198,7 +1268,7 @@ public class BiometricServiceTest {
|
|||||||
eq(0) /* vendorCode */);
|
eq(0) /* vendorCode */);
|
||||||
|
|
||||||
// Request for weak auth works
|
// Request for weak auth works
|
||||||
resetReceiver();
|
resetReceivers();
|
||||||
authenticators = Authenticators.BIOMETRIC_WEAK;
|
authenticators = Authenticators.BIOMETRIC_WEAK;
|
||||||
assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
|
assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
|
||||||
invokeCanAuthenticate(mBiometricService, authenticators));
|
invokeCanAuthenticate(mBiometricService, authenticators));
|
||||||
@@ -1217,7 +1287,7 @@ public class BiometricServiceTest {
|
|||||||
anyInt() /* sysUiSessionId */);
|
anyInt() /* sysUiSessionId */);
|
||||||
|
|
||||||
// Requesting strong and credential, when credential is setup
|
// Requesting strong and credential, when credential is setup
|
||||||
resetReceiver();
|
resetReceivers();
|
||||||
authenticators = Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL;
|
authenticators = Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL;
|
||||||
when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true);
|
when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true);
|
||||||
assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
|
assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
|
||||||
@@ -1244,7 +1314,7 @@ public class BiometricServiceTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resetReceiver();
|
resetReceivers();
|
||||||
authenticators = Authenticators.BIOMETRIC_STRONG;
|
authenticators = Authenticators.BIOMETRIC_STRONG;
|
||||||
assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
|
assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
|
||||||
invokeCanAuthenticate(mBiometricService, authenticators));
|
invokeCanAuthenticate(mBiometricService, authenticators));
|
||||||
@@ -1449,9 +1519,12 @@ public class BiometricServiceTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetReceiver() {
|
private void resetReceivers() {
|
||||||
mReceiver1 = mock(IBiometricServiceReceiver.class);
|
mReceiver1 = mock(IBiometricServiceReceiver.class);
|
||||||
mReceiver2 = mock(IBiometricServiceReceiver.class);
|
mReceiver2 = mock(IBiometricServiceReceiver.class);
|
||||||
|
|
||||||
|
when(mReceiver1.asBinder()).thenReturn(mock(Binder.class));
|
||||||
|
when(mReceiver2.asBinder()).thenReturn(mock(Binder.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetStatusBar() {
|
private void resetStatusBar() {
|
||||||
|
|||||||
Reference in New Issue
Block a user