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

Commit a8b50a6f authored by Curtis Belmonte's avatar Curtis Belmonte
Browse files

Make BiometricService check for internal permission

Previously, some paths through BiometricService needed to be accessible
by apps. Now that external calls are routed through AuthService instead,
we can check for the system-only USE_BIOMETRIC_INTERNAL permission
everywhere that we had been checking for USE_BIOMETRIC in
BiometricService.

In order for this to be enforced properly, we also need to move some
of the permission checks that were previously in BiometricService to
AuthService, which is now the primary entry point for applications
invoking the relevant biometric APIs.

Test: com.android.server.biometrics
Test: Manually verified functionality using support biometric demo app

Bug: 148971767
Change-Id: Ieab61276c6375b0d674f73e1833edabc8700fe74
parent a8f15a5d
Loading
Loading
Loading
Loading
+8 −5
Original line number Diff line number Diff line
@@ -29,13 +29,15 @@ interface IBiometricService {
    // Requests authentication. The service choose the appropriate biometric to use, and show
    // the corresponding BiometricDialog.
    void authenticate(IBinder token, long sessionId, int userId,
            IBiometricServiceReceiver receiver, String opPackageName, in Bundle bundle);
            IBiometricServiceReceiver receiver, String opPackageName, in Bundle bundle,
            int callingUid, int callingPid, int callingUserId);

    // Cancel authentication for the given sessionId
    void cancelAuthentication(IBinder token, String opPackageName);
    // Cancel authentication for the given session.
    void cancelAuthentication(IBinder token, String opPackageName, int callingUid, int callingPid,
            int callingUserId);

    // Checks if biometrics can be used.
    int canAuthenticate(String opPackageName, int userId, int authenticators);
    int canAuthenticate(String opPackageName, int userId, int callingUserId, int authenticators);

    // Checks if any biometrics are enrolled.
    boolean hasEnrolledBiometrics(int userId, String opPackageName);
@@ -47,7 +49,8 @@ interface IBiometricService {
            IBiometricAuthenticator authenticator);

    // Register callback for when keyguard biometric eligibility changes.
    void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback);
    void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback,
            int callingUserId);

    // Explicitly set the active user.
    void setActiveUser(int userId);
+87 −32
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import static android.hardware.biometrics.BiometricManager.Authenticators;

import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.IAuthService;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
@@ -38,6 +39,7 @@ import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.face.IFaceService;
import android.hardware.fingerprint.IFingerprintService;
import android.hardware.iris.IIrisService;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
@@ -133,14 +135,12 @@ public class AuthService extends SystemService {
        public void authenticate(IBinder token, long sessionId, int userId,
                IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle)
                throws RemoteException {
            final int callingUserId = UserHandle.getCallingUserId();

            // In the BiometricServiceBase, do the AppOps and foreground check.
            // Only allow internal clients to authenticate with a different userId.
            final int callingUserId = UserHandle.getCallingUserId();
            if (userId == callingUserId) {
                // Check the USE_BIOMETRIC permission here.
                checkPermission();
            } else {
                // Only allow internal clients to authenticate with a different userId
                Slog.w(TAG, "User " + callingUserId + " is requesting authentication of userid: "
                        + userId);
                checkInternalPermission();
@@ -150,23 +150,52 @@ public class AuthService extends SystemService {
                Slog.e(TAG, "Unable to authenticate, one or more null arguments");
                return;
            }
            mBiometricService.authenticate(token, sessionId, userId, receiver, opPackageName,
                    bundle);

            // Only allow internal clients to enable non-public options.
            if (bundle.getBoolean(BiometricPrompt.EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS)
                    || bundle.getBoolean(BiometricPrompt.KEY_USE_DEFAULT_TITLE, false)) {
                checkInternalPermission();
            }

            final int callingUid = Binder.getCallingUid();
            final int callingPid = Binder.getCallingPid();
            final long identity = Binder.clearCallingIdentity();
            try {
                mBiometricService.authenticate(
                        token, sessionId, userId, receiver, opPackageName, bundle, callingUid,
                        callingPid, callingUserId);
            } finally {
                Binder.restoreCallingIdentity(identity);
            }
        }

        @Override
        public void cancelAuthentication(IBinder token, String opPackageName)
                throws RemoteException {
            checkPermission();

            if (token == null || opPackageName == null) {
                Slog.e(TAG, "Unable to authenticate, one or more null arguments");
                return;
            }
            mBiometricService.cancelAuthentication(token, opPackageName);

            final int callingUid = Binder.getCallingUid();
            final int callingPid = Binder.getCallingPid();
            final int callingUserId = UserHandle.getCallingUserId();
            final long identity = Binder.clearCallingIdentity();
            try {
                mBiometricService.cancelAuthentication(token, opPackageName, callingUid,
                        callingPid, callingUserId);
            } finally {
                Binder.restoreCallingIdentity(identity);
            }
        }

        @Override
        public int canAuthenticate(String opPackageName, int userId,
                @Authenticators.Types int authenticators) throws RemoteException {

            // Only allow internal clients to call canAuthenticate with a different userId.
            final int callingUserId = UserHandle.getCallingUserId();
            Slog.d(TAG, "canAuthenticate, userId: " + userId + ", callingUserId: " + callingUserId
                    + ", authenticators: " + authenticators);
@@ -175,33 +204,61 @@ public class AuthService extends SystemService {
            } else {
                checkPermission();
            }
            return mBiometricService.canAuthenticate(opPackageName, userId, authenticators);

            final long identity = Binder.clearCallingIdentity();
            try {
                return mBiometricService.canAuthenticate(
                        opPackageName, userId, callingUserId, authenticators);
            } finally {
                Binder.restoreCallingIdentity(identity);
            }
        }

        @Override
        public boolean hasEnrolledBiometrics(int userId, String opPackageName)
                throws RemoteException {
            checkInternalPermission();
            final long identity = Binder.clearCallingIdentity();
            try {
                return mBiometricService.hasEnrolledBiometrics(userId, opPackageName);
            } finally {
                Binder.restoreCallingIdentity(identity);
            }
        }

        @Override
        public void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback)
                throws RemoteException {
        public void registerEnabledOnKeyguardCallback(
                IBiometricEnabledOnKeyguardCallback callback) throws RemoteException {
            checkInternalPermission();
            mBiometricService.registerEnabledOnKeyguardCallback(callback);
            final int callingUserId = UserHandle.getCallingUserId();
            final long identity = Binder.clearCallingIdentity();
            try {
                mBiometricService.registerEnabledOnKeyguardCallback(callback, callingUserId);
            } finally {
                Binder.restoreCallingIdentity(identity);
            }
        }

        @Override
        public void setActiveUser(int userId) throws RemoteException {
            checkInternalPermission();
            final long identity = Binder.clearCallingIdentity();
            try {
                mBiometricService.setActiveUser(userId);
            } finally {
                Binder.restoreCallingIdentity(identity);
            }
        }

        @Override
        public void resetLockout(byte[] token) throws RemoteException {
            checkInternalPermission();
            final long identity = Binder.clearCallingIdentity();
            try {
                mBiometricService.resetLockout(token);
            } finally {
                Binder.restoreCallingIdentity(identity);
            }
        }
    }

@@ -216,8 +273,23 @@ public class AuthService extends SystemService {
        mImpl = new AuthServiceImpl();
    }

    private void registerAuthenticator(SensorConfig config) throws RemoteException {
    @Override
    public void onStart() {
        mBiometricService = mInjector.getBiometricService();

        final String[] configs = mInjector.getConfiguration(getContext());
        for (String config : configs) {
            try {
                registerAuthenticator(new SensorConfig(config));
            } catch (RemoteException e) {
                Slog.e(TAG, "Remote exception", e);
            }
        }

        mInjector.publishBinderService(this, mImpl);
    }

    private void registerAuthenticator(SensorConfig config) throws RemoteException {
        Slog.d(TAG, "Registering ID: " + config.mId
                + " Modality: " + config.mModality
                + " Strength: " + config.mStrength);
@@ -267,23 +339,6 @@ public class AuthService extends SystemService {
                authenticator);
    }

    @Override
    public void onStart() {
        mBiometricService = mInjector.getBiometricService();

        final String[] configs = mInjector.getConfiguration(getContext());

        for (int i = 0; i < configs.length; i++) {
            try {
                registerAuthenticator(new SensorConfig(configs[i]));
            } catch (RemoteException e) {
                Slog.e(TAG, "Remote exception", e);
            }

        }

        mInjector.publishBinderService(this, mImpl);
    }

    private void checkInternalPermission() {
        getContext().enforceCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL,
+43 −79
Original line number Diff line number Diff line
@@ -16,9 +16,7 @@

package com.android.server.biometrics;

import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.Manifest.permission.USE_FINGERPRINT;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_IRIS;
@@ -32,7 +30,6 @@ import android.app.admin.DevicePolicyManager;
import android.app.trust.ITrustManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
@@ -318,7 +315,10 @@ public class BiometricService extends SystemService {
                    SomeArgs args = (SomeArgs) msg.obj;
                    handleCancelAuthentication(
                            (IBinder) args.arg1 /* token */,
                            (String) args.arg2 /* opPackageName */);
                            (String) args.arg2 /* opPackageName */,
                            args.argi1 /* callingUid */,
                            args.argi2 /* callingPid */,
                            args.argi3 /* callingUserId */);
                    args.recycle();
                    break;
                }
@@ -543,8 +543,7 @@ public class BiometricService extends SystemService {
    final IBiometricServiceReceiverInternal mInternalReceiver =
            new IBiometricServiceReceiverInternal.Stub() {
        @Override
        public void onAuthenticationSucceeded(boolean requireConfirmation, byte[] token)
                throws RemoteException {
        public void onAuthenticationSucceeded(boolean requireConfirmation, byte[] token) {
            SomeArgs args = SomeArgs.obtain();
            args.arg1 = requireConfirmation;
            args.arg2 = token;
@@ -552,8 +551,7 @@ public class BiometricService extends SystemService {
        }

        @Override
        public void onAuthenticationFailed()
                throws RemoteException {
        public void onAuthenticationFailed() {
            Slog.v(TAG, "onAuthenticationFailed");
            mHandler.obtainMessage(MSG_ON_AUTHENTICATION_REJECTED).sendToTarget();
        }
@@ -624,22 +622,9 @@ public class BiometricService extends SystemService {

        @Override // Binder call
        public void authenticate(IBinder token, long sessionId, int userId,
                IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle)
                throws RemoteException {
            final int callingUid = Binder.getCallingUid();
            final int callingPid = Binder.getCallingPid();
            final int callingUserId = UserHandle.getCallingUserId();

            // In the BiometricServiceBase, check do the AppOps and foreground check.
            if (userId == callingUserId) {
                // Check the USE_BIOMETRIC permission here.
                checkPermission();
            } else {
                // Only allow internal clients to authenticate with a different userId
                Slog.w(TAG, "User " + callingUserId + " is requesting authentication of userid: "
                        + userId);
                IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle,
                int callingUid, int callingPid, int callingUserId) {
            checkInternalPermission();
            }

            if (token == null || receiver == null || opPackageName == null || bundle == null) {
                Slog.e(TAG, "Unable to authenticate, one or more null arguments");
@@ -650,19 +635,10 @@ public class BiometricService extends SystemService {
                throw new SecurityException("Invalid authenticator configuration");
            }

            if (bundle.getBoolean(BiometricPrompt.EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS)) {
                checkInternalPermission();
            }

            Utils.combineAuthenticatorBundles(bundle);

            // Check the usage of this in system server. Need to remove this check if it becomes a
            // public API.
            final boolean useDefaultTitle =
                    bundle.getBoolean(BiometricPrompt.KEY_USE_DEFAULT_TITLE, false);
            if (useDefaultTitle) {
                checkInternalPermission();
                // Set the default title if necessary
            // Set the default title if necessary.
            if (bundle.getBoolean(BiometricPrompt.KEY_USE_DEFAULT_TITLE, false)) {
                if (TextUtils.isEmpty(bundle.getCharSequence(BiometricPrompt.KEY_TITLE))) {
                    bundle.putCharSequence(BiometricPrompt.KEY_TITLE,
                            getContext().getString(R.string.biometric_dialog_default_title));
@@ -684,29 +660,28 @@ public class BiometricService extends SystemService {
        }

        @Override // Binder call
        public void cancelAuthentication(IBinder token, String opPackageName)
                throws RemoteException {
            checkPermission();
        public void cancelAuthentication(IBinder token, String opPackageName,
                int callingUid, int callingPid, int callingUserId) {
            checkInternalPermission();

            SomeArgs args = SomeArgs.obtain();
            args.arg1 = token;
            args.arg2 = opPackageName;
            args.argi1 = callingUid;
            args.argi2 = callingPid;
            args.argi3 = callingUserId;
            mHandler.obtainMessage(MSG_CANCEL_AUTHENTICATION, args).sendToTarget();
        }

        @Override // Binder call
        public int canAuthenticate(String opPackageName, int userId,
        public int canAuthenticate(String opPackageName, int userId, int callingUserId,
                @Authenticators.Types int authenticators) {
            checkInternalPermission();

            Slog.d(TAG, "canAuthenticate: User=" + userId
                    + ", Caller=" + UserHandle.getCallingUserId()
                    + ", Caller=" + callingUserId
                    + ", Authenticators=" + authenticators);

            if (userId != UserHandle.getCallingUserId()) {
                checkInternalPermission();
            } else {
                checkPermission();
            }

            if (!Utils.isValidAuthenticatorConfig(authenticators)) {
                throw new SecurityException("Invalid authenticator configuration");
            }
@@ -715,14 +690,11 @@ public class BiometricService extends SystemService {
            bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);

            int biometricConstantsResult = BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE;
            final long ident = Binder.clearCallingIdentity();
            try {
                biometricConstantsResult = checkAndGetAuthenticators(userId, bundle, opPackageName,
                        false /* checkDevicePolicyManager */).second;
            } catch (RemoteException e) {
                Slog.e(TAG, "Remote exception", e);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }

            return Utils.biometricConstantsToBiometricManager(biometricConstantsResult);
@@ -732,7 +704,6 @@ public class BiometricService extends SystemService {
        public boolean hasEnrolledBiometrics(int userId, String opPackageName) {
            checkInternalPermission();

            final long ident = Binder.clearCallingIdentity();
            try {
                for (AuthenticatorWrapper authenticator : mAuthenticators) {
                    if (authenticator.impl.hasEnrolledTemplates(userId, opPackageName)) {
@@ -741,9 +712,8 @@ public class BiometricService extends SystemService {
                }
            } catch (RemoteException e) {
                Slog.e(TAG, "Remote exception", e);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }

            return false;
        }

@@ -791,14 +761,14 @@ public class BiometricService extends SystemService {
        }

        @Override // Binder call
        public void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback)
                throws RemoteException {
        public void registerEnabledOnKeyguardCallback(
                IBiometricEnabledOnKeyguardCallback callback, int callingUserId) {
            checkInternalPermission();

            mEnabledOnKeyguardCallbacks.add(new EnabledOnKeyguardCallback(callback));
            try {
                callback.onChanged(BiometricSourceType.FACE,
                        mSettingObserver.getFaceEnabledOnKeyguard(),
                        UserHandle.getCallingUserId());
                        mSettingObserver.getFaceEnabledOnKeyguard(), callingUserId);
            } catch (RemoteException e) {
                Slog.w(TAG, "Remote exception", e);
            }
@@ -807,30 +777,26 @@ public class BiometricService extends SystemService {
        @Override // Binder call
        public void setActiveUser(int userId) {
            checkInternalPermission();
            final long ident = Binder.clearCallingIdentity();

            try {
                for (AuthenticatorWrapper authenticator : mAuthenticators) {
                    authenticator.impl.setActiveUser(userId);
                }
            } catch (RemoteException e) {
                Slog.e(TAG, "Remote exception", e);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }

        @Override // Binder call
        public void resetLockout(byte[] token) {
            checkInternalPermission();
            final long ident = Binder.clearCallingIdentity();

            try {
                for (AuthenticatorWrapper authenticator : mAuthenticators) {
                    authenticator.impl.resetLockout(token);
                }
            } catch (RemoteException e) {
                Slog.e(TAG, "Remote exception", e);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }
    }
@@ -840,14 +806,6 @@ public class BiometricService extends SystemService {
                "Must have USE_BIOMETRIC_INTERNAL permission");
    }

    private void checkPermission() {
        if (getContext().checkCallingOrSelfPermission(USE_FINGERPRINT)
                != PackageManager.PERMISSION_GRANTED) {
            getContext().enforceCallingOrSelfPermission(USE_BIOMETRIC,
                    "Must have USE_BIOMETRIC permission");
        }
    }

    /**
     * Class for injecting dependencies into BiometricService.
     * TODO(b/141025588): Replace with a dependency injection framework (e.g. Guice, Dagger).
@@ -1440,7 +1398,9 @@ public class BiometricService extends SystemService {
                    mCurrentAuthSession.mClientReceiver.onDialogDismissed(reason);
                    // Cancel authentication. Skip the token/package check since we are cancelling
                    // from system server. The interface is permission protected so this is fine.
                    cancelInternal(null /* token */, null /* package */, false /* fromClient */);
                    cancelInternal(null /* token */, null /* package */,
                            mCurrentAuthSession.mCallingUid, mCurrentAuthSession.mCallingPid,
                            mCurrentAuthSession.mCallingUserId, false /* fromClient */);
                    break;

                case BiometricPrompt.DISMISSED_REASON_USER_CANCEL:
@@ -1451,7 +1411,9 @@ public class BiometricService extends SystemService {
                    );
                    // Cancel authentication. Skip the token/package check since we are cancelling
                    // from system server. The interface is permission protected so this is fine.
                    cancelInternal(null /* token */, null /* package */, false /* fromClient */);
                    cancelInternal(null /* token */, null /* package */, Binder.getCallingUid(),
                            Binder.getCallingPid(), UserHandle.getCallingUserId(),
                            false /* fromClient */);
                    break;

                case BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED:
@@ -1501,7 +1463,9 @@ public class BiometricService extends SystemService {

        // Cancel authentication. Skip the token/package check since we are cancelling
        // from system server. The interface is permission protected so this is fine.
        cancelInternal(null /* token */, null /* package */, false /* fromClient */);
        cancelInternal(null /* token */, null /* package */, Binder.getCallingUid(),
                Binder.getCallingPid(), UserHandle.getCallingUserId(),
                false /* fromClient */);

        mCurrentAuthSession.mState = STATE_SHOWING_DEVICE_CREDENTIAL;
    }
@@ -1671,7 +1635,8 @@ public class BiometricService extends SystemService {
        }
    }

    private void handleCancelAuthentication(IBinder token, String opPackageName) {
    private void handleCancelAuthentication(IBinder token, String opPackageName, int callingUid,
            int callingPid, int callingUserId) {
        if (token == null || opPackageName == null) {
            Slog.e(TAG, "Unable to cancel, one or more null arguments");
            return;
@@ -1694,14 +1659,13 @@ public class BiometricService extends SystemService {
                Slog.e(TAG, "Remote exception", e);
            }
        } else {
            cancelInternal(token, opPackageName, true /* fromClient */);
            cancelInternal(token, opPackageName, callingUid, callingPid, callingUserId,
                    true /* fromClient */);
        }
    }

    void cancelInternal(IBinder token, String opPackageName, boolean fromClient) {
        final int callingUid = Binder.getCallingUid();
        final int callingPid = Binder.getCallingPid();
        final int callingUserId = UserHandle.getCallingUserId();
    void cancelInternal(IBinder token, String opPackageName, int callingUid, int callingPid,
            int callingUserId, boolean fromClient) {

        if (mCurrentAuthSession == null) {
            Slog.w(TAG, "Skipping cancelInternal");
+9 −3
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ import android.hardware.fingerprint.IFingerprintService;
import android.hardware.iris.IIrisService;
import android.os.Binder;
import android.os.Bundle;
import android.os.UserHandle;

import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -136,7 +137,10 @@ public class AuthServiceTest {
                eq(userId),
                eq(mReceiver),
                eq(TEST_OP_PACKAGE_NAME),
                eq(bundle));
                eq(bundle),
                eq(Binder.getCallingUid()),
                eq(Binder.getCallingPid()),
                eq(UserHandle.getCallingUserId()));
    }

    @Test
@@ -147,7 +151,7 @@ public class AuthServiceTest {
        final int userId = 0;
        final int expectedResult = BIOMETRIC_SUCCESS;
        final int authenticators = 0;
        when(mBiometricService.canAuthenticate(anyString(), anyInt(), anyInt()))
        when(mBiometricService.canAuthenticate(anyString(), anyInt(), anyInt(), anyInt()))
                .thenReturn(expectedResult);

        final int result = mAuthService.mImpl
@@ -158,6 +162,7 @@ public class AuthServiceTest {
        verify(mBiometricService).canAuthenticate(
                eq(TEST_OP_PACKAGE_NAME),
                eq(userId),
                eq(UserHandle.getCallingUserId()),
                eq(authenticators));
    }

@@ -196,7 +201,8 @@ public class AuthServiceTest {
        mAuthService.mImpl.registerEnabledOnKeyguardCallback(callback);

        waitForIdle();
        verify(mBiometricService).registerEnabledOnKeyguardCallback(eq(callback));
        verify(mBiometricService).registerEnabledOnKeyguardCallback(
                eq(callback), eq(UserHandle.getCallingUserId()));
    }

    @Test
+11 −4

File changed.

Preview size limit exceeded, changes collapsed.