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

Commit cb2ce6f1 authored by Jim Miller's avatar Jim Miller
Browse files

Fix bug where fingerprint events can be delivered to the wrong client

- Make FingerprintService more closely track the expected state of fingerprintd.
- Don't switch to a new operation until fingerprintd completes previous operation.
- Refactor clients into separate classes and add tracking logic.
- Add missing enumerate()/cancelEnumeration() methods to IFingerprintDaemon
- Make late-binding decision of "foregroundness" of activity so that it's
decided in the order the events are actually handled.
- Add more logging so we can determine FingerprintService state when errors occur.
- Cache a copy of authenticator_id from the last time it was set so we don't
interrupt the driver during actual authentication.
- Don't allow clients to access authenticator_id unless they're current.

Fixes: 27902478, 26273819
Change-Id: Ic1f9e30bd89bcdbb8fe7f69e0545e68339317721
parent 4f9b759d
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -517,7 +517,8 @@ public class FingerprintManager {

        if (mService != null) try {
            mEnrollmentCallback = callback;
            mService.enroll(mToken, token, userId, mServiceReceiver, flags);
            mService.enroll(mToken, token, userId, mServiceReceiver, flags,
                    mContext.getOpPackageName());
        } catch (RemoteException e) {
            Log.w(TAG, "Remote exception in enroll: ", e);
            if (callback != null) {
+2 −0
Original line number Diff line number Diff line
@@ -35,4 +35,6 @@ interface IFingerprintDaemon {
    int closeHal();
    void init(IFingerprintDaemonCallback callback);
    int postEnroll();
    int enumerate();
    int cancelEnumeration();
}
+1 −1
Original line number Diff line number Diff line
@@ -35,7 +35,7 @@ interface IFingerprintService {

    // Start fingerprint enrollment
    void enroll(IBinder token, in byte [] cryptoToken, int groupId, IFingerprintServiceReceiver receiver,
            int flags);
            int flags, String opPackageName);

    // Cancel enrollment in progress
    void cancelEnrollment(IBinder token);
+156 −0
Original line number Diff line number Diff line
/**
 * Copyright (C) 2016 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.fingerprint;

import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.MetricsProto.MetricsEvent;

import android.content.Context;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.IFingerprintDaemon;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.os.IBinder;
import android.os.RemoteException;
import android.system.ErrnoException;
import android.util.Slog;

/**
 * A class to keep track of the authentication state for a given client.
 */
public abstract class AuthenticationClient extends ClientMonitor {
    private long mOpId;

    public abstract boolean handleFailedAttempt();
    public abstract void resetFailedAttempts();

    public AuthenticationClient(Context context, long halDeviceId, IBinder token,
            IFingerprintServiceReceiver receiver, int userId, int groupId, long opId,
            boolean restricted, String owner) {
        super(context, halDeviceId, token, receiver, userId, groupId, restricted, owner);
        mOpId = opId;
    }

    @Override
    public boolean onAuthenticated(int fingerId, int groupId) {
        boolean result = false;
        boolean authenticated = fingerId != 0;

        IFingerprintServiceReceiver receiver = getReceiver();
        if (receiver != null) {
            try {
                MetricsLogger.action(getContext(), MetricsEvent.ACTION_FINGERPRINT_AUTH,
                        authenticated);
                if (!authenticated) {
                    receiver.onAuthenticationFailed(getHalDeviceId());
                } else {
                    if (DEBUG) {
                        Slog.v(TAG, "onAuthenticated(owner=" + getOwnerString()
                                + ", id=" + fingerId + ", gp=" + groupId + ")");
                    }
                    Fingerprint fp = !getIsRestricted()
                            ? new Fingerprint("" /* TODO */, groupId, fingerId, getHalDeviceId())
                            : null;
                    receiver.onAuthenticationSucceeded(getHalDeviceId(), fp);
                }
            } catch (RemoteException e) {
                Slog.w(TAG, "Failed to notify Authenticated:", e);
                result = true; // client failed
            }
        } else {
            result = true; // client not listening
        }
        if (fingerId == 0) {
            if (receiver != null) {
                FingerprintUtils.vibrateFingerprintError(getContext());
            }
            // allow system-defined limit of number of attempts before giving up
            result |= handleFailedAttempt();
        } else {
            if (receiver != null) {
                FingerprintUtils.vibrateFingerprintSuccess(getContext());
            }
            result |= true; // we have a valid fingerprint, done
            resetFailedAttempts();
        }
        return result;
    }

    /**
     * Start authentication
     */
    @Override
    public int start() {
        IFingerprintDaemon daemon = getFingerprintDaemon();
        if (daemon == null) {
            Slog.w(TAG, "start authentication: no fingeprintd!");
            return ERROR_ESRCH;
        }
        try {
            final int result = daemon.authenticate(mOpId, getGroupId());
            if (result != 0) {
                Slog.w(TAG, "startAuthentication failed, result=" + result);
                onError(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE);
                return result;
            }
            if (DEBUG) Slog.w(TAG, "client " + getOwnerString() + " is authenticating...");
        } catch (RemoteException e) {
            Slog.e(TAG, "startAuthentication failed", e);
            return ERROR_ESRCH;
        }
        return 0; // success
    }

    @Override
    public int stop(boolean initiatedByClient) {
        IFingerprintDaemon daemon = getFingerprintDaemon();
        if (daemon == null) {
            Slog.w(TAG, "stopAuthentication: no fingeprintd!");
            return ERROR_ESRCH;
        }
        try {
            final int result = daemon.cancelAuthentication();
            if (result != 0) {
                Slog.w(TAG, "stopAuthentication failed, result=" + result);
                return result;
            }
            if (DEBUG) Slog.w(TAG, "client " + getOwnerString() + " is no longer authenticating");
        } catch (RemoteException e) {
            Slog.e(TAG, "stopAuthentication failed", e);
            return ERROR_ESRCH;
        }
        return 0; // success
    }

    @Override
    public boolean onEnrollResult(int fingerId, int groupId, int rem) {
        if (DEBUG) Slog.w(TAG, "onEnrollResult() called for authenticate!");
        return true; // Invalid for Authenticate
    }

    @Override
    public boolean onRemoved(int fingerId, int groupId) {
        if (DEBUG) Slog.w(TAG, "onRemoved() called for authenticate!");
        return true; // Invalid for Authenticate
    }

    @Override
    public boolean onEnumerationResult(int fingerId, int groupId) {
        if (DEBUG) Slog.w(TAG, "onEnumerationResult() called for authenticate!");
        return true; // Invalid for Authenticate
    }
}
+211 −0
Original line number Diff line number Diff line
/**
 * Copyright (C) 2016 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.fingerprint;

import android.Manifest;
import android.content.Context;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.IFingerprintDaemon;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;

import java.util.NoSuchElementException;

/**
 * Abstract base class for keeping track and dispatching events from fingerprintd to the
 * the current client.  Subclasses are responsible for coordinating the interaction with
 * fingerprintd for the specific action (e.g. authenticate, enroll, enumerate, etc.).
 */
public abstract class ClientMonitor implements IBinder.DeathRecipient {
    protected static final String TAG = FingerprintService.TAG; // TODO: get specific name
    protected static final int ERROR_ESRCH = 3; // Likely fingerprintd is dead. See errno.h.
    protected static final boolean DEBUG = FingerprintService.DEBUG;
    private IBinder mToken;
    private IFingerprintServiceReceiver mReceiver;
    private int mUserId;
    private int mGroupId;
    private boolean mIsRestricted; // True if client does not have MANAGE_FINGERPRINT permission
    private String mOwner;
    private Context mContext;
    private long mHalDeviceId;

    /**
     * @param context context of FingerprintService
     * @param halDeviceId the HAL device ID of the associated fingerprint hardware
     * @param token a unique token for the client
     * @param receiver recipient of related events (e.g. authentication)
     * @param userId userId for the fingerprint set
     * @param groupId groupId for the fingerprint set
     * @param restricted whether or not client has the {@link Manifest#MANAGE_FINGERPRINT}
     * permission
     * @param owner name of the client that owns this
     */
    public ClientMonitor(Context context, long halDeviceId, IBinder token,
            IFingerprintServiceReceiver receiver, int userId, int groupId,boolean restricted,
            String owner) {
        mContext = context;
        mHalDeviceId = halDeviceId;
        mToken = token;
        mReceiver = receiver;
        mUserId = userId;
        mGroupId = groupId;
        mIsRestricted = restricted;
        mOwner = owner;
        try {
            token.linkToDeath(this, 0);
        } catch (RemoteException e) {
            Slog.w(TAG, "caught remote exception in linkToDeath: ", e);
        }
    }

    /**
     * Contacts fingerprintd to start the client.
     * @return 0 on succes, errno from driver on failure
     */
    public abstract int start();

    /**
     * Contacts fingerprintd to stop the client.
     * @param initiatedByClient whether the operation is at the request of a client
     */
    public abstract int stop(boolean initiatedByClient);

    /**
     * Method to explicitly poke powermanager on events
     */
    public abstract void notifyUserActivity();

    /**
     * Gets the fingerprint daemon from the cached state in the container class.
     */
    public abstract IFingerprintDaemon getFingerprintDaemon();

    // Event callbacks from driver. Inappropriate calls is flagged/logged by the
    // respective client (e.g. enrolling shouldn't get authenticate events).
    // All of these return 'true' if the operation is completed and it's ok to move
    // to the next client (e.g. authentication accepts or rejects a fingerprint).
    public abstract boolean onEnrollResult(int fingerId, int groupId, int rem);
    public abstract boolean onAuthenticated(int fingerId, int groupId);
    public abstract boolean onRemoved(int fingerId, int groupId);
    public abstract boolean onEnumerationResult(int fingerId, int groupId);

    /**
     * Called when we get notification from fingerprintd that an image has been acquired.
     * Common to authenticate and enroll.
     * @param acquiredInfo info about the current image acquisition
     * @return true if client should be removed
     */
    public boolean onAcquired(int acquiredInfo) {
        if (mReceiver == null)
            return true; // client not connected
        try {
            mReceiver.onAcquired(getHalDeviceId(), acquiredInfo);
            return false; // acquisition continues...
        } catch (RemoteException e) {
            Slog.w(TAG, "Failed to invoke sendAcquired:", e);
            return true; // client failed
        } finally {
            // Good scans will keep the device awake
            if (acquiredInfo == FingerprintManager.FINGERPRINT_ACQUIRED_GOOD) {
                notifyUserActivity();
            }
        }
    }

    /**
     * Called when we get notification from fingerprintd that an error has occurred with the
     * current operation. Common to authenticate, enroll, enumerate and remove.
     * @param error
     * @return true if client should be removed
     */
    public boolean onError(int error) {
        if (mReceiver != null) {
            try {
                mReceiver.onError(getHalDeviceId(), error);
            } catch (RemoteException e) {
                Slog.w(TAG, "Failed to invoke sendError:", e);
            }
        }
        return true; // errors always remove current client
    }

    public void destroy() {
        if (mToken != null) {
            try {
                mToken.unlinkToDeath(this, 0);
            } catch (NoSuchElementException e) {
                // TODO: remove when duplicate call bug is found
                Slog.e(TAG, "destroy(): " + this + ":", new Exception("here"));
            }
            mToken = null;
        }
        mReceiver = null;
    }

    @Override
    public void binderDied() {
        mToken = null;
        mReceiver = null;
        onError(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE);
    }

    @Override
    protected void finalize() throws Throwable {
        try {
            if (mToken != null) {
                if (DEBUG) Slog.w(TAG, "removing leaked reference: " + mToken);
                onError(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE);
            }
        } finally {
            super.finalize();
        }
    }

    public final Context getContext() {
        return mContext;
    }

    public final long getHalDeviceId() {
        return mHalDeviceId;
    }

    public final String getOwnerString() {
        return mOwner;
    }

    public final IFingerprintServiceReceiver getReceiver() {
        return mReceiver;
    }

    public final boolean getIsRestricted() {
        return mIsRestricted;
    }

    public final int getUserId() {
        return mUserId;
    }

    public final int getGroupId() {
        return mGroupId;
    }

    public final IBinder getToken() {
        return mToken;
    }
}
Loading