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

Commit 199d5d93 authored by Brad Ebinger's avatar Brad Ebinger
Browse files

Ensure installed packages do not leak when calling placeCall

By checking if the target PhoneAccountHandle packageName matches
the calling package before determining selfManaged attributes,
we stop the leak of installed packages that may be associated
with a specific test PhoneAccountHandle.

Bug: 231988638
Bug: 265321471
Test: atest TelecomUnitTests; atest CtsTelecomTestCases
Change-Id: I1ab81c69a32f398152fb5e00615bd395f96dadba
parent e0a031df
Loading
Loading
Loading
Loading
+35 −31
Original line number Diff line number Diff line
@@ -1743,7 +1743,6 @@ public class TelecomServiceImpl {
                enforceCallingPackage(callingPackage, "placeCall");

                PhoneAccountHandle phoneAccountHandle = null;
                boolean clearPhoneAccountHandleExtra = false;
                if (extras != null) {
                    phoneAccountHandle = extras.getParcelable(
                            TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
@@ -1752,33 +1751,42 @@ public class TelecomServiceImpl {
                        extras.remove(TelecomManager.EXTRA_IS_HANDOVER);
                    }
                }
                boolean isSelfManaged = phoneAccountHandle != null &&
                ComponentName phoneAccountComponentName = phoneAccountHandle != null
                        ? phoneAccountHandle.getComponentName() : null;
                String phoneAccountPackageName = phoneAccountComponentName != null
                        ? phoneAccountComponentName.getPackageName() : null;
                boolean isCallerOwnerOfPhoneAccount =
                        callingPackage.equals(phoneAccountPackageName);
                boolean isSelfManagedPhoneAccount =
                        isSelfManagedConnectionService(phoneAccountHandle);
                if (isSelfManaged) {
                    try {
                // Ensure the app's calling package matches the PhoneAccount package name before
                // checking self-managed status so that we do not leak installed package
                // information.
                boolean isSelfManagedRequest = isCallerOwnerOfPhoneAccount &&
                        isSelfManagedPhoneAccount;
                if (isSelfManagedRequest) {
                    // The package name of the caller matches the package name of the
                    // PhoneAccountHandle, so ensure the app has MANAGE_OWN_CALLS permission if
                    // self-managed.
                    mContext.enforceCallingOrSelfPermission(
                            Manifest.permission.MANAGE_OWN_CALLS,
                                "Self-managed ConnectionServices require "
                                        + "MANAGE_OWN_CALLS permission.");
                    } catch (SecurityException e) {
                        // Fallback to use mobile network to avoid disclosing phone account handle
                        // package information
                        clearPhoneAccountHandleExtra = true;
                    }

                    if (!clearPhoneAccountHandleExtra && !callingPackage.equals(
                            phoneAccountHandle.getComponentName().getPackageName())
                            && !canCallPhone(callingPackage, callingFeatureId,
                            "Self-managed ConnectionServices require MANAGE_OWN_CALLS permission.");
                } else if (!canCallPhone(callingPackage, callingFeatureId,
                        "CALL_PHONE permission required to place calls.")) {
                        // The caller is not allowed to place calls, so fallback to use mobile
                        // network.
                        clearPhoneAccountHandleExtra = true;
                    }
                } else if (!canCallPhone(callingPackage, callingFeatureId, "placeCall")) {
                    // not self-managed, so CALL_PHONE is required.
                    mAnomalyReporter.reportAnomaly(PLACE_CALL_SECURITY_EXCEPTION_ERROR_UUID,
                            PLACE_CALL_SECURITY_EXCEPTION_ERROR_MSG);
                    throw new SecurityException("Package " + callingPackage
                            + " is not allowed to place phone calls");
                    throw new SecurityException(
                            "CALL_PHONE permission required to place calls.");
                }

                // An application can not place a call with a self-managed PhoneAccount that
                // they do not own. If this is the case (and the app has CALL_PHONE permission),
                // remove the PhoneAccount from the request and place the call as if it was a
                // managed call request with no PhoneAccount specified.
                if (!isCallerOwnerOfPhoneAccount && isSelfManagedPhoneAccount) {
                    // extras can not be null if isSelfManagedPhoneAccount is true
                    extras.remove(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
                }

                // Note: we can still get here for the default/system dialer, even if the Phone
@@ -1809,15 +1817,11 @@ public class TelecomServiceImpl {
                        final Intent intent = new Intent(hasCallPrivilegedPermission ?
                                Intent.ACTION_CALL_PRIVILEGED : Intent.ACTION_CALL, handle);
                        if (extras != null) {
                            if (clearPhoneAccountHandleExtra) {
                                extras.remove(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
                            }
                            extras.setDefusable(true);
                            intent.putExtras(extras);
                        }
                        mUserCallIntentProcessorFactory.create(mContext, userHandle)
                                .processIntent(
                                        intent, callingPackage, isSelfManaged,
                                .processIntent(intent, callingPackage, isSelfManagedRequest,
                                        (hasCallAppOp && hasCallPermission),
                                        true /* isLocalInvocation */);
                    } finally {
+9 −19
Original line number Diff line number Diff line
@@ -1214,20 +1214,14 @@ public class TelecomServiceImplTest extends TelecomTestCase {
        // ConnectionService.
        extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT);

        doReturn(PackageManager.PERMISSION_DENIED)
                .when(mContext).checkCallingPermission(CALL_PHONE);
        doReturn(PackageManager.PERMISSION_DENIED)
                .when(mContext).checkCallingPermission(CALL_PRIVILEGED);
        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
                eq(Manifest.permission.MANAGE_OWN_CALLS), anyString());

        try {
            mTSIBinder.placeCall(handle, extras, PACKAGE_NAME, null);
            placeCallTestHelper(handle, extras, /*isSelfManagedExpected*/ true,
                    /*shouldNonEmergencyBeAllowed*/ false);
            fail("Expected SecurityException because MANAGE_OWN_CALLS is not set");
        } catch(SecurityException e) {
            fail("Unexpected SecurityException - MANAGE_OWN_CALLS is not set, so call should be "
                    + "placed with no PhoneAccount");
            // expected
        }
    }

@@ -1283,21 +1277,15 @@ public class TelecomServiceImplTest extends TelecomTestCase {
        // pass MANAGE_OWN_CALLS check, but do not have CALL PHONE
        doNothing().when(mContext).enforceCallingOrSelfPermission(
                eq(Manifest.permission.MANAGE_OWN_CALLS), anyString());
        doReturn(PackageManager.PERMISSION_GRANTED)
                .when(mContext).checkCallingPermission(CALL_PHONE);
        doReturn(PackageManager.PERMISSION_DENIED)
                .when(mContext).checkCallingPermission(CALL_PRIVILEGED);
        doNothing().when(mContext).enforceCallingOrSelfPermission(eq(CALL_PHONE), anyString());
        when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(),
                nullable(String.class), nullable(String.class)))
                .thenReturn(AppOpsManager.MODE_ERRORED);
        try {
            mTSIBinder.placeCall(handle, extras, PACKAGE_NAME + "2", null);
            placeCallTestHelper(handle, extras, /*isSelfManagedExpected*/ true,
                    /*shouldNonEmergencyBeAllowed*/ false);
            fail("Expected a SecurityException - CALL_PHONE app op is denied");
        } catch(SecurityException e) {
            fail("Unexpected SecurityException - MANAGE_OWN_CALLS is set, so call should be "
                    + "placed with no PhoneAccount");
            // expected
        }
    }

@@ -1338,7 +1326,7 @@ public class TelecomServiceImplTest extends TelecomTestCase {
            fail("Unexpected SecurityException - CTS is default dialer and MANAGE_OWN_CALLS is not"
                    + " required. Exception: " + e);
        }
        placeCallTestHelper(handle, extras, /*isSelfManagedExpected*/ true,
        placeCallTestHelper(handle, extras, /*isSelfManagedExpected*/ false,
                /*shouldNonEmergencyBeAllowed*/ true);
    }

@@ -1408,8 +1396,8 @@ public class TelecomServiceImplTest extends TelecomTestCase {
    }

    /**
     * Since this is a self-managed call being requested, even though we do not have CALL_PHONE
     * permissions, we still consider non-emergency allowed because this is a self-managed call.
     * Since this is a self-managed call being requested, so ensure we report the call as
     * self-managed and without non-emergency permissions.
     */
    @SmallTest
    @Test
@@ -2094,6 +2082,8 @@ public class TelecomServiceImplTest extends TelecomTestCase {
    }

    private static boolean areBundlesEqual(Bundle b1, Bundle b2) {
        if (b1.keySet().size() != b2.keySet().size()) return false;

        for (String key1 : b1.keySet()) {
            if (!b1.get(key1).equals(b2.get(key1))) {
                return false;