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

Commit 2c375efa authored by Michael Groover's avatar Michael Groover
Browse files

Guard ICC ID behind new identifier access requirements

In Android 10 access to device identifiers was limited to apps with
the READ_PRIVILEGED_PHONE_STATE permission, carrier privileges, the
READ_DEVICE_IDENTIFIERS appop set to allow, or those that pass a
device / profile owner check. TelephonyManager#getSimSerialNumber
was guarded behind these new access requirements, but the same value
is still accessible via SubscriptionInfo#getIccId. This change
clears out the ICC ID in any returned SubscriptionInfo objects if
the caller does not meet the new identifier access requirements.

Bug: 131909991
Test: atest SubscriptionControllerTest
Change-Id: Iab02eef23a9b34d3e5eaceaabc8affd3725f5f3c
parent 5252036e
Loading
Loading
Loading
Loading
+90 −36
Original line number Diff line number Diff line
@@ -263,6 +263,24 @@ public class SubscriptionController extends ISub.Stub {
                Manifest.permission.READ_PRIVILEGED_PHONE_STATE, message);
    }

    /**
     * Returns whether the {@code callingPackage} has access to subscriber identifiers on the
     * specified {@code subId} using the provided {@code message} in any resulting
     * SecurityException.
     */
    private boolean hasSubscriberIdentifierAccess(int subId, String callingPackage,
            String callingFeatureId, String message) {
        try {
            return TelephonyPermissions.checkCallingOrSelfReadSubscriberIdentifiers(mContext, subId,
                    callingPackage, callingFeatureId, message);
        } catch (SecurityException e) {
            // A SecurityException indicates that the calling package is targeting at least the
            // minimum level that enforces identifier access restrictions and the new access
            // requirements are not met.
            return false;
        }
    }

    /**
     * Broadcast when SubscriptionInfo has changed
     * FIXME: Hopefully removed if the API council accepts SubscriptionInfoListener
@@ -513,17 +531,21 @@ public class SubscriptionController extends ISub.Stub {

        // Now that all security checks passes, perform the operation as ourselves.
        final long identity = Binder.clearCallingIdentity();
        List<SubscriptionInfo> subList;
        try {
            List<SubscriptionInfo> subList = getActiveSubscriptionInfoList(
            subList = getActiveSubscriptionInfoList(
                    mContext.getOpPackageName(), mContext.getFeatureId());
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
        if (subList != null) {
            for (SubscriptionInfo si : subList) {
                if (si.getSubscriptionId() == subId) {
                    if (VDBG) {
                        logd("[getActiveSubscriptionInfo]+ subId=" + subId + " subInfo=" + si);
                    }

                        return si;
                    return conditionallyRemoveIdentifiers(si, callingPackage, callingFeatureId,
                            "getActiveSubscriptionInfo");
                }
            }
        }
@@ -531,9 +553,6 @@ public class SubscriptionController extends ISub.Stub {
            logd("[getActiveSubscriptionInfo]- subId=" + subId
                    + " subList=" + subList + " subInfo=null");
        }
        } finally {
            Binder.restoreCallingIdentity(identity);
        }

        return null;
    }
@@ -625,9 +644,13 @@ public class SubscriptionController extends ISub.Stub {

        // Now that all security checks passes, perform the operation as ourselves.
        final long identity = Binder.clearCallingIdentity();
        List<SubscriptionInfo> subList;
        try {
            List<SubscriptionInfo> subList = getActiveSubscriptionInfoList(
            subList = getActiveSubscriptionInfoList(
                    mContext.getOpPackageName(), mContext.getFeatureId());
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
        if (subList != null) {
            for (SubscriptionInfo si : subList) {
                if (si.getSimSlotIndex() == slotIndex) {
@@ -635,7 +658,8 @@ public class SubscriptionController extends ISub.Stub {
                        logd("[getActiveSubscriptionInfoForSimSlotIndex]+ slotIndex="
                                + slotIndex + " subId=" + si);
                    }
                        return si;
                    return conditionallyRemoveIdentifiers(si, callingPackage, callingFeatureId,
                            "getActiveSubscriptionInfoForSimSlotIndex");
                }
            }
            if (DBG) {
@@ -647,9 +671,7 @@ public class SubscriptionController extends ISub.Stub {
                logd("[getActiveSubscriptionInfoForSimSlotIndex]+ subList=null");
            }
        }
        } finally {
            Binder.restoreCallingIdentity(identity);
        }


        return null;
    }
@@ -3379,7 +3401,10 @@ public class SubscriptionController extends ISub.Stub {
            return TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subId,
                    callingPackage, callingFeatureId, "getSubscriptionsInGroup")
                    || info.canManageSubscription(mContext, callingPackage);
        }).collect(Collectors.toList());
        }).map(subscriptionInfo -> conditionallyRemoveIdentifiers(subscriptionInfo,
                callingPackage, callingFeatureId, "getSubscriptionInfoList"))
        .collect(Collectors.toList());

    }

    public ParcelUuid getGroupUuid(int subId) {
@@ -3652,6 +3677,14 @@ public class SubscriptionController extends ISub.Stub {
                    SubscriptionManager.INVALID_SUBSCRIPTION_ID, Binder.getCallingPid(),
                    Binder.getCallingUid(), callingPackage, callingFeatureId,
                    "getSubscriptionInfoList");
            // If the calling package has the READ_PHONE_STATE permission then check if the caller
            // also has access to subscriber identifiers to ensure that the ICC ID and any other
            // unique identifiers are removed if the caller should not have access.
            if (canReadAllPhoneState) {
                canReadAllPhoneState = hasSubscriberIdentifierAccess(
                        SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage,
                        callingFeatureId, "getSubscriptionInfoList");
            }
        } catch (SecurityException e) {
            canReadAllPhoneState = false;
        }
@@ -3672,11 +3705,32 @@ public class SubscriptionController extends ISub.Stub {
                        } catch (SecurityException e) {
                            return false;
                        }
                    })
                    }).map(subscriptionInfo -> conditionallyRemoveIdentifiers(subscriptionInfo,
                            callingPackage, callingFeatureId, "getSubscriptionInfoList"))
                    .collect(Collectors.toList());
        }
    }

    /**
     * Conditionally removes identifiers from the provided {@code subInfo} if the {@code
     * callingPackage} does not meet the access requirements for identifiers and returns the
     * potentially modified object..
     *
     * <p>If the caller does not meet the access requirements for identifiers a clone of the
     * provided SubscriptionInfo is created and modified to avoid altering SubscriptionInfo objects
     * in a cache.
     */
    private SubscriptionInfo conditionallyRemoveIdentifiers(SubscriptionInfo subInfo,
            String callingPackage, String callingFeatureId, String message) {
        SubscriptionInfo result = subInfo;
        if (!hasSubscriberIdentifierAccess(subInfo.getSubscriptionId(), callingPackage,
                callingFeatureId, message)) {
            result = new SubscriptionInfo(subInfo);
            result.clearIccId();
        }
        return result;
    }

    private synchronized boolean addToSubIdList(int slotIndex, int subId, int subscriptionType) {
        ArrayList<Integer> subIdsList = sSlotIndexToSubIds.get(slotIndex);
        if (subIdsList == null) {
+207 −0
Original line number Diff line number Diff line
@@ -82,6 +82,8 @@ public class SubscriptionControllerTest extends TelephonyTest {
    private static final String MAC_ADDRESS_PREFIX = "mac_";
    private static final String DISPLAY_NAME_PREFIX = "my_phone_";

    private static final String UNAVAILABLE_ICCID = "";

    @Before
    public void setUp() throws Exception {
        super.setUp("SubscriptionControllerTest");
@@ -1015,6 +1017,211 @@ public class SubscriptionControllerTest extends TelephonyTest {
        assertTrue("active sub ids = " + subIds, Arrays.equals(subIds, new int[]{2, 1}));
    }

    @Test
    public void testGetActiveSubscriptionInfoWithNoPermissions() throws Exception {
        // If the calling package does not have the READ_PHONE_STATE permission or carrier
        // privileges then getActiveSubscriptionInfo should throw a SecurityException;
        testInsertSim();
        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
        int subId = getFirstSubId();

        try {
            mSubscriptionControllerUT.getActiveSubscriptionInfo(subId, mCallingPackage,
                    mCallingFeature);
            fail("getActiveSubscriptionInfo should fail when invoked with no permissions");
        } catch (SecurityException expected) {
        }
    }

    @Test
    public void testGetActiveSubscriptionInfoWithReadPhoneState() throws Exception {
        // If the calling package only has the READ_PHONE_STATE permission then
        // getActiveSubscriptionInfo should still return a result but the ICC ID should not be
        // available.
        testInsertSim();
        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
        setupMocksForTelephonyPermissions();
        int subId = getFirstSubId();

        SubscriptionInfo subscriptionInfo = mSubscriptionControllerUT.getActiveSubscriptionInfo(
                subId, mCallingPackage, mCallingFeature);
        assertNotNull(subscriptionInfo);
        assertEquals(UNAVAILABLE_ICCID, subscriptionInfo.getIccId());
    }

    @Test
    public void testGetActiveSubscriptionWithPrivilegedPermission() throws Exception {
        // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier
        // privileges the ICC ID should be available in the SubscriptionInfo.
        testInsertSim();
        int subId = getFirstSubId();

        SubscriptionInfo subscriptionInfo = mSubscriptionControllerUT.getActiveSubscriptionInfo(
                subId, mCallingPackage, mCallingFeature);
        assertNotNull(subscriptionInfo);
        assertTrue(subscriptionInfo.getIccId().length() > 0);
    }

    @Test
    public void testGetActiveSubscriptionInfoForSimSlotIndexWithNoPermission() throws Exception {
        // If the calling package does not have the READ_PHONE_STATE permission or carrier
        // privileges then getActiveSubscriptionInfoForSimSlotIndex should throw a
        // SecurityException.
        testInsertSim();
        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);

        try {
            mSubscriptionControllerUT.getActiveSubscriptionInfoForSimSlotIndex(0, mCallingPackage,
                    mCallingFeature);
            fail("getActiveSubscriptionInfoForSimSlotIndex should fail when invoked with no "
                    + "permissions");
        } catch (SecurityException expected) {
        }
    }

    @Test
    public void testGetActiveSubscriptionInfoForSimSlotIndexWithReadPhoneState() throws Exception {
        // If the calling package only has the READ_PHONE_STATE permission then
        // getActiveSubscriptionInfoForSimlSlotIndex should still return the SubscriptionInfo but
        // the ICC ID should not be available.
        testInsertSim();
        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
        setupMocksForTelephonyPermissions();

        SubscriptionInfo subscriptionInfo =
                mSubscriptionControllerUT.getActiveSubscriptionInfoForSimSlotIndex(0,
                        mCallingPackage, mCallingFeature);
        assertNotNull(subscriptionInfo);
        assertEquals(UNAVAILABLE_ICCID, subscriptionInfo.getIccId());
    }

    @Test
    public void testGetActiveSubscriptionInfoForSimSlotIndexWithPrivilegedPermission()
            throws Exception {
        // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier
        // privileges the ICC ID should be available in the SubscriptionInfo.
        testInsertSim();

        SubscriptionInfo subscriptionInfo =
                mSubscriptionControllerUT.getActiveSubscriptionInfoForSimSlotIndex(0,
                        mCallingPackage, mCallingFeature);
        assertNotNull(subscriptionInfo);
        assertTrue(subscriptionInfo.getIccId().length() > 0);
    }

    @Test
    public void testGetActiveSubscriptionInfoListWithNoPermission() throws Exception {
        // If the calling package does not have the READ_PHONE_STATE permission or carrier
        // privileges then getActiveSubscriptionInfoList should return a list with 0 elements.
        testInsertSim();
        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);

        List<SubscriptionInfo> subInfoList =
                mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage,
                        mCallingFeature);
        assertNotNull(subInfoList);
        assertTrue(subInfoList.size() == 0);
    }

    @Test
    public void testGetActiveSubscriptionInfoListWithReadPhoneState() throws Exception {
        // If the calling package only has the READ_PHONE_STATE permission then
        // getActiveSubscriptionInfoList should still return the list of SubscriptionInfo objects
        // but the ICC ID should not be available.
        testInsertSim();
        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
        setupMocksForTelephonyPermissions();

        List<SubscriptionInfo> subInfoList =
                mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage,
                        mCallingFeature);
        assertTrue(subInfoList.size() > 0);
        for (SubscriptionInfo info : subInfoList) {
            assertEquals(UNAVAILABLE_ICCID, info.getIccId());
        }
    }

    @Test
    public void testGetActiveSubscriptionInfoListWithPrivilegedPermission() throws Exception {
        // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier
        // privileges the ICC ID should be available in the SubscriptionInfo objects in the List.
        testInsertSim();

        List<SubscriptionInfo> subInfoList =
                mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage,
                        mCallingFeature);
        assertTrue(subInfoList.size() > 0);
        for (SubscriptionInfo info : subInfoList) {
            assertTrue(info.getIccId().length() > 0);
        }
    }

    @Test
    public void testGetSubscriptionsInGroupWithNoPermission() throws Exception {
        // If the calling package does not have the READ_PHONE_STATE permission or carrier
        // privileges then getSubscriptionsInGroup should throw a SecurityException when the
        // READ_PHONE_STATE permission check is performed.
        ParcelUuid groupUuid = setupGetSubscriptionsInGroupTest();
        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);

        try {
            mSubscriptionControllerUT.getSubscriptionsInGroup(groupUuid, mCallingPackage,
                    mCallingFeature);
            fail("getSubscriptionsInGroup should fail when invoked with no permissions");
        } catch (SecurityException expected) {
        }
    }

    @Test
    public void testGetSubscriptionsInGroupWithReadPhoneState() throws Exception {
        // If the calling package only has the READ_PHONE_STATE permission then
        // getSubscriptionsInGroup should still return the list of SubscriptionInfo objects
        // but the ICC ID should not be available.
        ParcelUuid groupUuid = setupGetSubscriptionsInGroupTest();
        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
        setupMocksForTelephonyPermissions();

        List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT.getSubscriptionsInGroup(
                groupUuid, mCallingPackage, mCallingFeature);
        assertTrue(subInfoList.size() > 0);
        for (SubscriptionInfo info : subInfoList) {
            assertEquals(UNAVAILABLE_ICCID, info.getIccId());
        }
    }

    @Test
    public void testGetSubscriptionsInGroupWithPrivilegedPermission() throws Exception {
        // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier
        // privileges the ICC ID should be available in the SubscriptionInfo objects in the List.
        ParcelUuid groupUuid = setupGetSubscriptionsInGroupTest();

        List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT.getSubscriptionsInGroup(
                groupUuid, mCallingPackage, mCallingFeature);
        assertTrue(subInfoList.size() > 0);
        for (SubscriptionInfo info : subInfoList) {
            assertTrue(info.getIccId().length() > 0);
        }
    }

    private ParcelUuid setupGetSubscriptionsInGroupTest() throws Exception {
        testInsertSim();
        int[] subIdList = new int[]{getFirstSubId()};
        ParcelUuid groupUuid = mSubscriptionControllerUT.createSubscriptionGroup(subIdList,
                mCallingPackage);
        assertNotNull(groupUuid);
        return groupUuid;
    }

    private int getFirstSubId() throws Exception {
        int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false);
        assertTrue(subIds != null && subIds.length != 0);
        return subIds[0];
    }

    @Test
    public void testGetEnabledSubscriptionIdSingleSIM() {
        // A single SIM device may have logical slot 0 mapped to physical slot 1
+10 −0
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;

import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.content.ContentResolver;
import android.content.Context;
@@ -282,6 +283,7 @@ public abstract class TelephonyTest {
    protected EuiccManager mEuiccManager;
    protected PackageManager mPackageManager;
    protected ConnectivityManager mConnectivityManager;
    protected AppOpsManager mAppOpsManager;
    protected SimulatedCommands mSimulatedCommands;
    protected ContextFixture mContextFixture;
    protected Context mContext;
@@ -415,6 +417,7 @@ public abstract class TelephonyTest {
        mConnectivityManager = (ConnectivityManager)
                mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
        mPackageManager = mContext.getPackageManager();
        mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);

        //mTelephonyComponentFactory
        doReturn(mTelephonyComponentFactory).when(mTelephonyComponentFactory).inject(anyString());
@@ -734,6 +737,13 @@ public abstract class TelephonyTest {
        doReturn(mApplicationInfo).when(mPackageManager).getApplicationInfoAsUser(eq(TAG), anyInt(),
                any());

        // TelephonyPermissions also checks to see if the calling package has been granted
        // identifier access via an appop; ensure this query does not allow identifier access for
        // any packages.
        doReturn(AppOpsManager.MODE_DEFAULT).when(mAppOpsManager).noteOpNoThrow(
                eq(AppOpsManager.OPSTR_READ_DEVICE_IDENTIFIERS), anyInt(), anyString(),
                nullable(String.class), nullable(String.class));

        // TelephonyPermissions queries DeviceConfig to determine if the identifier access
        // restrictions should be enabled; this results in a NPE when DeviceConfig uses
        // Activity.currentActivity.getContentResolver as the resolver for Settings.Config.getString