Clear BiometricScheduler when biometric HAL dies

Operations cannot be expected to complete if the HAL dies. The
easiest solution is to clear any existing operation, as well as
any pending operations.

It's dangerous if we leave pending operations in the queue, since
for some HALs, subsequent operations depend on preceeding operations.
For example, updateActiveUser + authenticate.

Fixes: 172683967
Test: atest com.android.server.biometrics

Change-Id: I50b5234d4b382b08767dc359c4a13404e73897c2
This commit is contained in:
Kevin Chyn
2020-11-16 16:44:44 -08:00
parent 92c2f231ed
commit 60984c6c32
10 changed files with 401 additions and 14 deletions

View File

@@ -543,6 +543,10 @@ public class BiometricScheduler {
return mCurrentOperation.clientMonitor;
}
public int getCurrentPendingCount() {
return mPendingOperations.size();
}
public void recordCrashState() {
if (mCrashStates.size() >= CrashState.NUM_ENTRIES) {
mCrashStates.removeFirst();
@@ -568,4 +572,13 @@ public class BiometricScheduler {
pw.println("Crash State " + crashState);
}
}
/**
* Clears the scheduler of anything work-related. This should be used for example when the
* HAL dies.
*/
public void reset() {
mPendingOperations.clear();
mCurrentOperation = null;
}
}

View File

@@ -219,7 +219,7 @@ public abstract class ClientMonitor<T> extends LoggableMonitor implements IBinde
return mSensorId;
}
public final T getFreshDaemon() {
public T getFreshDaemon() {
return mLazyDaemon.getDaemon();
}

View File

@@ -41,6 +41,7 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.ClientMonitor;
@@ -69,7 +70,8 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
@NonNull private final Context mContext;
@NonNull private final String mHalInstanceName;
@NonNull private final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports
@NonNull @VisibleForTesting
final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports
@NonNull private final ClientMonitor.LazyDaemon<IFace> mLazyDaemon;
@NonNull private final Handler mHandler;
@NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;
@@ -585,8 +587,11 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
mHandler.post(() -> {
mDaemon = null;
for (int i = 0; i < mSensors.size(); i++) {
final Sensor sensor = mSensors.valueAt(i);
final int sensorId = mSensors.keyAt(i);
PerformanceTracker.getInstanceForSensorId(sensorId).incrementHALDeathCount();
sensor.getScheduler().recordCrashState();
sensor.getScheduler().reset();
}
});
}

View File

@@ -333,16 +333,17 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
}
@VisibleForTesting
public Face10(@NonNull Context context, int sensorId,
Face10(@NonNull Context context, int sensorId,
@BiometricManager.Authenticators.Types int strength,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
boolean supportsSelfIllumination, int maxTemplatesAllowed) {
boolean supportsSelfIllumination, int maxTemplatesAllowed,
@NonNull BiometricScheduler scheduler) {
mSensorProperties = new FaceSensorPropertiesInternal(sensorId,
Utils.authenticatorStrengthToPropertyStrength(strength),
maxTemplatesAllowed, false /* supportsFaceDetect */, supportsSelfIllumination);
mContext = context;
mSensorId = sensorId;
mScheduler = new BiometricScheduler(TAG, null /* gestureAvailabilityTracker */);
mScheduler = scheduler;
mHandler = new Handler(Looper.getMainLooper());
mUsageStats = new UsageStats(context);
mAuthenticatorIds = new HashMap<>();
@@ -369,7 +370,8 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
@NonNull LockoutResetDispatcher lockoutResetDispatcher) {
this(context, sensorId, strength, lockoutResetDispatcher,
context.getResources().getBoolean(R.bool.config_faceAuthSupportsSelfIllumination),
context.getResources().getInteger(R.integer.config_faceMaxTemplatesPerUser));
context.getResources().getInteger(R.integer.config_faceMaxTemplatesPerUser),
new BiometricScheduler(TAG, null /* gestureAvailabilityTracker */));
}
@Override
@@ -388,12 +390,13 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
interruptable.onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
0 /* vendorCode */);
mScheduler.recordCrashState();
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED,
BiometricsProtoEnums.MODALITY_FACE,
BiometricsProtoEnums.ISSUE_HAL_DEATH);
}
mScheduler.recordCrashState();
mScheduler.reset();
});
}

View File

@@ -41,6 +41,7 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.ClientMonitor;
@@ -67,7 +68,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
@NonNull private final Context mContext;
@NonNull private final String mHalInstanceName;
@NonNull private final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports
@NonNull @VisibleForTesting
final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports
@NonNull private final ClientMonitor.LazyDaemon<IFingerprint> mLazyDaemon;
@NonNull private final Handler mHandler;
@NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;
@@ -607,8 +609,11 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
mDaemon = null;
for (int i = 0; i < mSensors.size(); i++) {
final Sensor sensor = mSensors.valueAt(i);
final int sensorId = mSensors.keyAt(i);
PerformanceTracker.getInstanceForSensorId(sensorId).incrementHALDeathCount();
sensor.getScheduler().recordCrashState();
sensor.getScheduler().reset();
}
});
}

View File

@@ -386,12 +386,13 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
interruptable.onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
0 /* vendorCode */);
mScheduler.recordCrashState();
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED,
BiometricsProtoEnums.MODALITY_FINGERPRINT,
BiometricsProtoEnums.ISSUE_HAL_DEATH);
}
mScheduler.recordCrashState();
mScheduler.reset();
});
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.biometrics.sensors.face.aidl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.hardware.biometrics.common.CommonProps;
import android.hardware.biometrics.face.SensorProps;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.ClientMonitor;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
@Presubmit
@SmallTest
public class FaceProviderTest {
private static final String TAG = "FaceProviderTest";
@Mock
private Context mContext;
@Mock
private UserManager mUserManager;
private SensorProps[] mSensorProps;
private LockoutResetDispatcher mLockoutResetDispatcher;
private FaceProvider mFaceProvider;
private static void waitForIdle() {
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>());
final SensorProps sensor1 = new SensorProps();
sensor1.commonProps = new CommonProps();
sensor1.commonProps.sensorId = 0;
final SensorProps sensor2 = new SensorProps();
sensor2.commonProps = new CommonProps();
sensor2.commonProps.sensorId = 1;
mSensorProps = new SensorProps[] {sensor1, sensor2};
mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
mFaceProvider = new FaceProvider(mContext, mSensorProps, TAG,
mLockoutResetDispatcher);
}
@SuppressWarnings("rawtypes")
@Test
public void halServiceDied_resetsAllSchedulers() {
assertEquals(mSensorProps.length, mFaceProvider.getSensorProperties().size());
// Schedule N operations on each sensor
final int numFakeOperations = 10;
for (SensorProps prop : mSensorProps) {
final BiometricScheduler scheduler =
mFaceProvider.mSensors.get(prop.commonProps.sensorId).getScheduler();
for (int i = 0; i < numFakeOperations; i++) {
final ClientMonitor testMonitor = mock(ClientMonitor.class);
when(testMonitor.getFreshDaemon()).thenReturn(new Object());
scheduler.scheduleClientMonitor(testMonitor);
}
}
waitForIdle();
// The right amount of pending and current operations are scheduled
for (SensorProps prop : mSensorProps) {
final BiometricScheduler scheduler =
mFaceProvider.mSensors.get(prop.commonProps.sensorId).getScheduler();
assertEquals(numFakeOperations - 1, scheduler.getCurrentPendingCount());
assertNotNull(scheduler.getCurrentClient());
}
// It's difficult to test the linkToDeath --> serviceDied path, so let's just invoke
// serviceDied directly.
mFaceProvider.binderDied();
waitForIdle();
// No pending operations, no current operation.
for (SensorProps prop : mSensorProps) {
final BiometricScheduler scheduler =
mFaceProvider.mSensors.get(prop.commonProps.sensorId).getScheduler();
assertNull(scheduler.getCurrentClient());
assertEquals(0, scheduler.getCurrentPendingCount());
}
}
}

View File

@@ -14,8 +14,9 @@
* limitations under the License.
*/
package com.android.server.biometrics.sensors.face;
package com.android.server.biometrics.sensors.face.hidl;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
@@ -28,8 +29,8 @@ import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.face.hidl.Face10;
import org.junit.Before;
import org.junit.Test;
@@ -49,6 +50,8 @@ public class Face10Test {
private Context mContext;
@Mock
private UserManager mUserManager;
@Mock
private BiometricScheduler mScheduler;
private LockoutResetDispatcher mLockoutResetDispatcher;
private com.android.server.biometrics.sensors.face.hidl.Face10 mFace10;
@@ -68,7 +71,7 @@ public class Face10Test {
mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
mFace10 = new Face10(mContext, SENSOR_ID, BiometricManager.Authenticators.BIOMETRIC_STRONG,
mLockoutResetDispatcher, false /* supportsSelfIllumination */,
1 /* maxTemplatesAllowed */);
1 /* maxTemplatesAllowed */, mScheduler);
mBinder = new Binder();
}
@@ -78,4 +81,13 @@ public class Face10Test {
0 /* challenge */);
waitForIdle();
}
@Test
public void halServiceDied_resetsScheduler() {
// It's difficult to test the linkToDeath --> serviceDied path, so let's just invoke
// serviceDied directly.
mFace10.serviceDied(0 /* cookie */);
waitForIdle();
verify(mScheduler).reset();
}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.biometrics.sensors.fingerprint.aidl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.hardware.biometrics.common.CommonProps;
import android.hardware.biometrics.fingerprint.SensorProps;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.ClientMonitor;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
@Presubmit
@SmallTest
public class FingerprintProviderTest {
private static final String TAG = "FingerprintProviderTest";
@Mock
private Context mContext;
@Mock
private UserManager mUserManager;
@Mock
private GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
private SensorProps[] mSensorProps;
private LockoutResetDispatcher mLockoutResetDispatcher;
private FingerprintProvider mFingerprintProvider;
private static void waitForIdle() {
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>());
final SensorProps sensor1 = new SensorProps();
sensor1.commonProps = new CommonProps();
sensor1.commonProps.sensorId = 0;
final SensorProps sensor2 = new SensorProps();
sensor2.commonProps = new CommonProps();
sensor2.commonProps.sensorId = 1;
mSensorProps = new SensorProps[] {sensor1, sensor2};
mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
mFingerprintProvider = new FingerprintProvider(mContext, mSensorProps, TAG,
mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
}
@SuppressWarnings("rawtypes")
@Test
public void halServiceDied_resetsAllSchedulers() {
assertEquals(mSensorProps.length, mFingerprintProvider.getSensorProperties().size());
// Schedule N operations on each sensor
final int numFakeOperations = 10;
for (SensorProps prop : mSensorProps) {
final BiometricScheduler scheduler =
mFingerprintProvider.mSensors.get(prop.commonProps.sensorId).getScheduler();
for (int i = 0; i < numFakeOperations; i++) {
final ClientMonitor testMonitor = mock(ClientMonitor.class);
when(testMonitor.getFreshDaemon()).thenReturn(new Object());
scheduler.scheduleClientMonitor(testMonitor);
}
}
waitForIdle();
// The right amount of pending and current operations are scheduled
for (SensorProps prop : mSensorProps) {
final BiometricScheduler scheduler =
mFingerprintProvider.mSensors.get(prop.commonProps.sensorId).getScheduler();
assertEquals(numFakeOperations - 1, scheduler.getCurrentPendingCount());
assertNotNull(scheduler.getCurrentClient());
}
// It's difficult to test the linkToDeath --> serviceDied path, so let's just invoke
// serviceDied directly.
mFingerprintProvider.binderDied();
waitForIdle();
// No pending operations, no current operation.
for (SensorProps prop : mSensorProps) {
final BiometricScheduler scheduler =
mFingerprintProvider.mSensors.get(prop.commonProps.sensorId).getScheduler();
assertNull(scheduler.getCurrentClient());
assertEquals(0, scheduler.getCurrentPendingCount());
}
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.biometrics.sensors.fingerprint.hidl;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.res.Resources;
import android.hardware.biometrics.BiometricManager;
import android.os.Handler;
import android.os.Looper;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import com.android.internal.R;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
@Presubmit
@SmallTest
public class Fingerprint21Test {
private static final String TAG = "Fingerprint21Test";
private static final int SENSOR_ID = 1;
@Mock
private Context mContext;
@Mock
private Resources mResources;
@Mock
private UserManager mUserManager;
@Mock
Fingerprint21.HalResultController mHalResultController;
@Mock
private BiometricScheduler mScheduler;
private LockoutResetDispatcher mLockoutResetDispatcher;
private Fingerprint21 mFingerprint21;
private static void waitForIdle() {
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>());
when(mContext.getResources()).thenReturn(mResources);
when(mResources.getInteger(eq(R.integer.config_fingerprintMaxTemplatesPerUser)))
.thenReturn(5);
mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
mFingerprint21 = new Fingerprint21(mContext, mScheduler,
new Handler(Looper.getMainLooper()), SENSOR_ID,
BiometricManager.Authenticators.BIOMETRIC_WEAK, mLockoutResetDispatcher,
mHalResultController);
}
@Test
public void halServiceDied_resetsScheduler() {
// It's difficult to test the linkToDeath --> serviceDied path, so let's just invoke
// serviceDied directly.
mFingerprint21.serviceDied(0 /* cookie */);
waitForIdle();
verify(mScheduler).reset();
}
}