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

Commit e129e095 authored by Gil Cukierman's avatar Gil Cukierman Committed by Android (Google) Code Review
Browse files

Merge "Emit stats for cellular identifier disclosures" into main

parents 1d6f94ea 9ac31b06
Loading
Loading
Loading
Loading
+86 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.internal.telephony.metrics;

import android.telephony.CellularIdentifierDisclosure;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.TelephonyStatsLog;
import com.android.telephony.Rlog;

/**
 * Facilitates writing stats relating to cellular transparency features. Delegates the actual
 * writing of stats out to {@link TelephonyStatsLog}.
 */
public class CellularSecurityTransparencyStats {

    private static final String LOG_TAG = "CellularSecurityTransparencyStats";
    private static final String LOG_DESCRIPTOR_SIM_MCC = "SIM MCC";
    private static final String LOG_DESCRIPTOR_SIM_MNC = "SIM MNC";
    private static final String LOG_DESCRIPTOR_DISCLOSURE_MCC = "disclosure MCC";
    private static final String LOG_DESCRIPTOR_DISCLOSURE_MNC = "disclosure MNC";
    private static final int DEFAULT_PLMN_PART = -1;

    /**
     * Log an identifier disclosure to be written out to {@link TelephonyStatsLog}
     */
    public void logIdentifierDisclosure(CellularIdentifierDisclosure disclosure, String simMcc,
            String simMnc, boolean notificationsEnabled) {

        int mcc = parsePlmnPartOrDefault(simMcc, LOG_DESCRIPTOR_SIM_MCC);
        int mnc = parsePlmnPartOrDefault(simMnc, LOG_DESCRIPTOR_SIM_MNC);

        int disclosureMcc = DEFAULT_PLMN_PART;
        int disclosureMnc = DEFAULT_PLMN_PART;
        String plmn = disclosure.getPlmn();
        if (plmn != null) {
            String[] plmnParts = plmn.split("-");
            if (plmnParts.length == 2) {
                disclosureMcc = parsePlmnPartOrDefault(plmnParts[0], LOG_DESCRIPTOR_DISCLOSURE_MCC);
                disclosureMnc = parsePlmnPartOrDefault(plmnParts[1], LOG_DESCRIPTOR_DISCLOSURE_MNC);
            }
        }

        writeIdentifierDisclosure(mcc, mnc, disclosureMcc, disclosureMnc,
                disclosure.getCellularIdentifier(), disclosure.getNasProtocolMessage(),
                disclosure.isEmergency(), notificationsEnabled);

    }

    private int parsePlmnPartOrDefault(String input, String logDescriptor) {
        try {
            return Integer.parseInt(input);
        } catch (NumberFormatException e) {
            Rlog.d(LOG_TAG, "Failed to parse " + logDescriptor + ": " + input);
        }

        return DEFAULT_PLMN_PART;
    }

    /**
     * Write identifier disclosure data out to {@link TelephonyStatsLog}. This method is split
     * out to enable testing, since {@link TelephonyStatsLog} is a final static class.
     */
    @VisibleForTesting
    public void writeIdentifierDisclosure(int mcc, int mnc, int disclosureMcc, int disclosureMnc,
            int identifier, int protocolMessage, boolean isEmergency,
            boolean areNotificationsEnabled) {
        TelephonyStatsLog.write(TelephonyStatsLog.CELLULAR_IDENTIFIER_DISCLOSED, mcc, mnc,
                disclosureMcc, disclosureMnc, identifier, protocolMessage, isEmergency,
                areNotificationsEnabled);
    }
}
+40 −6
Original line number Diff line number Diff line
@@ -21,6 +21,9 @@ import android.telephony.CellularIdentifierDisclosure;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.metrics.CellularSecurityTransparencyStats;
import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
import com.android.internal.telephony.subscription.SubscriptionManagerService;
import com.android.telephony.Rlog;

import java.time.Instant;
@@ -62,13 +65,13 @@ public class CellularIdentifierDisclosureNotifier {
    // This object should only be accessed from within the thread of mSerializedWorkQueue. Access
    // outside of that thread would require additional synchronization.
    private Map<Integer, DisclosureWindow> mWindows;
    private SubscriptionManagerService mSubscriptionManagerService;
    private CellularSecurityTransparencyStats mCellularSecurityTransparencyStats;

    public CellularIdentifierDisclosureNotifier(CellularNetworkSecuritySafetySource safetySource) {
        this(
                Executors.newSingleThreadScheduledExecutor(),
                DEFAULT_WINDOW_CLOSE_DURATION_IN_MINUTES,
                TimeUnit.MINUTES,
                safetySource);
        this(Executors.newSingleThreadScheduledExecutor(), DEFAULT_WINDOW_CLOSE_DURATION_IN_MINUTES,
                TimeUnit.MINUTES, safetySource, SubscriptionManagerService.getInstance(),
                new CellularSecurityTransparencyStats());
    }

    /**
@@ -83,12 +86,16 @@ public class CellularIdentifierDisclosureNotifier {
            ScheduledExecutorService notificationQueue,
            long windowCloseDuration,
            TimeUnit windowCloseUnit,
            CellularNetworkSecuritySafetySource safetySource) {
            CellularNetworkSecuritySafetySource safetySource,
            SubscriptionManagerService subscriptionManagerService,
            CellularSecurityTransparencyStats cellularSecurityTransparencyStats) {
        mSerializedWorkQueue = notificationQueue;
        mWindowCloseDuration = windowCloseDuration;
        mWindowCloseUnit = windowCloseUnit;
        mWindows = new HashMap<>();
        mSafetySource = safetySource;
        mSubscriptionManagerService = subscriptionManagerService;
        mCellularSecurityTransparencyStats = cellularSecurityTransparencyStats;
    }

    /**
@@ -98,6 +105,8 @@ public class CellularIdentifierDisclosureNotifier {
    public void addDisclosure(Context context, int subId, CellularIdentifierDisclosure disclosure) {
        Rlog.d(TAG, "Identifier disclosure reported: " + disclosure);

        logDisclosure(subId, disclosure);

        synchronized (mEnabledLock) {
            if (!mEnabled) {
                Rlog.d(TAG, "Skipping disclosure because notifier was disabled.");
@@ -123,6 +132,31 @@ public class CellularIdentifierDisclosureNotifier {
        } // end mEnabledLock
    }

    private void logDisclosure(int subId, CellularIdentifierDisclosure disclosure) {
        try {
            mSerializedWorkQueue.execute(runLogDisclosure(subId, disclosure));
        } catch (RejectedExecutionException e) {
            Rlog.e(TAG, "Failed to schedule runLogDisclosure: " + e.getMessage());
        }
    }

    private Runnable runLogDisclosure(int subId,
            CellularIdentifierDisclosure disclosure) {
        return () -> {
            SubscriptionInfoInternal subInfo =
                    mSubscriptionManagerService.getSubscriptionInfoInternal(subId);
            String mcc = null;
            String mnc = null;
            if (subInfo != null) {
                mcc = subInfo.getMcc();
                mnc = subInfo.getMnc();
            }

            mCellularSecurityTransparencyStats.logIdentifierDisclosure(disclosure, mcc, mnc,
                    isEnabled());
        };
    }

    /**
     * Re-enable if previously disabled. This means that {@code addDisclsoure} will start tracking
     * disclosures again and potentially emitting notifications.
+93 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.internal.telephony.metrics;

import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;

import android.telephony.CellularIdentifierDisclosure;

import org.junit.Before;
import org.junit.Test;

public class CellularSecurityTransparencyStatsTest {

    private CellularSecurityTransparencyStats mCellularSecurityStats;

    @Before
    public void setUp() throws Exception {
        mCellularSecurityStats = spy(new CellularSecurityTransparencyStats());
    }

    @Test
    public void testLogIdentifierDisclosure_NullSimPlmn() {
        CellularIdentifierDisclosure disclosure = new CellularIdentifierDisclosure(
                CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST,
                CellularIdentifierDisclosure.CELLULAR_IDENTIFIER_IMSI, "123-456", true);

        mCellularSecurityStats.logIdentifierDisclosure(disclosure, null, null, true);

        verify(mCellularSecurityStats).writeIdentifierDisclosure(-1, -1, 123, 456,
                CellularIdentifierDisclosure.CELLULAR_IDENTIFIER_IMSI,
                CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST, true,
                true);
    }

    @Test
    public void testLogIdentifierDisclosure_badSimPlmn() {
        CellularIdentifierDisclosure disclosure = new CellularIdentifierDisclosure(
                CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST,
                CellularIdentifierDisclosure.CELLULAR_IDENTIFIER_IMSI, "123-456", true);

        mCellularSecurityStats.logIdentifierDisclosure(disclosure, "INCORRECTLY", "FORMATTED",
                true);

        verify(mCellularSecurityStats).writeIdentifierDisclosure(-1, -1, 123, 456,
                CellularIdentifierDisclosure.CELLULAR_IDENTIFIER_IMSI,
                CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST, true,
                true);
    }

    @Test
    public void testLogIdentifierDisclosure_badDisclosurePlmn() {
        CellularIdentifierDisclosure disclosure = new CellularIdentifierDisclosure(
                CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST,
                CellularIdentifierDisclosure.CELLULAR_IDENTIFIER_IMSI, "INCORRECT", true);

        mCellularSecurityStats.logIdentifierDisclosure(disclosure, "123", "456", true);

        verify(mCellularSecurityStats).writeIdentifierDisclosure(123, 456, -1, -1,
                CellularIdentifierDisclosure.CELLULAR_IDENTIFIER_IMSI,
                CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST, true,
                true);
    }

    @Test
    public void testLogIdentifierDisclosure_expectedGoodData() {
        CellularIdentifierDisclosure disclosure = new CellularIdentifierDisclosure(
                CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST,
                CellularIdentifierDisclosure.CELLULAR_IDENTIFIER_IMSI, "999-666", true);

        mCellularSecurityStats.logIdentifierDisclosure(disclosure, "123", "456", true);

        verify(mCellularSecurityStats).writeIdentifierDisclosure(123, 456, 999, 666,
                CellularIdentifierDisclosure.CELLULAR_IDENTIFIER_IMSI,
                CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST, true,
                true);

    }
}
+47 −41
Original line number Diff line number Diff line
@@ -29,12 +29,17 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.content.Context;
import android.telephony.CellularIdentifierDisclosure;

import com.android.internal.telephony.TestExecutorService;
import com.android.internal.telephony.metrics.CellularSecurityTransparencyStats;
import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
import com.android.internal.telephony.subscription.SubscriptionManagerService;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InOrder;
@@ -50,6 +55,9 @@ public class CellularIdentifierDisclosureNotifierTest {
    private static final int SUB_ID_2 = 2;
    private CellularIdentifierDisclosure mDislosure;
    private CellularNetworkSecuritySafetySource mSafetySource;
    private CellularSecurityTransparencyStats mStats;
    private TestExecutorService mExecutor;
    private SubscriptionManagerService mSubscriptionManagerService;
    private Context mContext;
    private InOrder mInOrder;

@@ -62,15 +70,16 @@ public class CellularIdentifierDisclosureNotifierTest {
                        "001001",
                        false);
        mSafetySource = mock(CellularNetworkSecuritySafetySource.class);
        mStats = mock(CellularSecurityTransparencyStats.class);
        mExecutor = new TestExecutorService();
        mSubscriptionManagerService = mock(SubscriptionManagerService.class);
        mContext = mock(Context.class);
        mInOrder = inOrder(mSafetySource);
    }

    @Test
    public void testInitializeDisabled() {
        TestExecutorService executor = new TestExecutorService();
        CellularIdentifierDisclosureNotifier notifier =
                new CellularIdentifierDisclosureNotifier(
                        executor, 15, TimeUnit.MINUTES, mSafetySource);
        CellularIdentifierDisclosureNotifier notifier = getNotifier();

        assertFalse(notifier.isEnabled());
        verify(mSafetySource, never()).setIdentifierDisclosureIssueEnabled(any(), anyBoolean());
@@ -78,10 +87,7 @@ public class CellularIdentifierDisclosureNotifierTest {

    @Test
    public void testDisableAddDisclosureNop() {
        TestExecutorService executor = new TestExecutorService();
        CellularIdentifierDisclosureNotifier notifier =
                new CellularIdentifierDisclosureNotifier(
                        executor, 15, TimeUnit.MINUTES, mSafetySource);
        CellularIdentifierDisclosureNotifier notifier = getNotifier();

        assertFalse(notifier.isEnabled());
        notifier.addDisclosure(mContext, SUB_ID_1, mDislosure);
@@ -92,10 +98,8 @@ public class CellularIdentifierDisclosureNotifierTest {

    @Test
    public void testAddDisclosureEmergencyNop() {
        TestExecutorService executor = new TestExecutorService();
        CellularIdentifierDisclosureNotifier notifier =
                new CellularIdentifierDisclosureNotifier(
                        executor, 15, TimeUnit.MINUTES, mSafetySource);
        CellularIdentifierDisclosureNotifier notifier = getNotifier();

        CellularIdentifierDisclosure emergencyDisclosure =
                new CellularIdentifierDisclosure(
                        CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST,
@@ -113,11 +117,7 @@ public class CellularIdentifierDisclosureNotifierTest {

    @Test
    public void testAddDisclosureCountIncrements() {
        TestExecutorService executor = new TestExecutorService();
        CellularIdentifierDisclosureNotifier notifier =
                new CellularIdentifierDisclosureNotifier(
                        executor, 15, TimeUnit.MINUTES, mSafetySource);

        CellularIdentifierDisclosureNotifier notifier = getNotifier();
        notifier.enable(mContext);

        for (int i = 0; i < 3; i++) {
@@ -135,28 +135,20 @@ public class CellularIdentifierDisclosureNotifierTest {

    @Test
    public void testSingleDisclosureStartAndEndTimesAreEqual() {
        TestExecutorService executor = new TestExecutorService();
        CellularIdentifierDisclosureNotifier notifier =
                new CellularIdentifierDisclosureNotifier(
                        executor, 15, TimeUnit.MINUTES, mSafetySource);

        CellularIdentifierDisclosureNotifier notifier = getNotifier();
        notifier.enable(mContext);

        notifier.addDisclosure(mContext, SUB_ID_1, mDislosure);

        assertEquals(1, notifier.getCurrentDisclosureCount(SUB_ID_1));
        assertTrue(notifier.getFirstOpen(SUB_ID_1).equals(notifier.getCurrentEnd(SUB_ID_1)));
        Assert.assertEquals(notifier.getFirstOpen(SUB_ID_1), notifier.getCurrentEnd(SUB_ID_1));
        mInOrder.verify(mSafetySource, times(1))
                .setIdentifierDisclosure(any(), eq(SUB_ID_1), eq(1), any(), any());
    }

    @Test
    public void testMultipleDisclosuresTimeWindows() {
        TestExecutorService executor = new TestExecutorService();
        CellularIdentifierDisclosureNotifier notifier =
                new CellularIdentifierDisclosureNotifier(
                        executor, 15, TimeUnit.MINUTES, mSafetySource);

        CellularIdentifierDisclosureNotifier notifier = getNotifier();
        notifier.enable(mContext);

        notifier.addDisclosure(mContext, SUB_ID_1, mDislosure);
@@ -175,10 +167,7 @@ public class CellularIdentifierDisclosureNotifierTest {

    @Test
    public void testAddDisclosureThenWindowClose() {
        TestExecutorService executor = new TestExecutorService();
        CellularIdentifierDisclosureNotifier notifier =
                new CellularIdentifierDisclosureNotifier(
                        executor, 15, TimeUnit.MINUTES, mSafetySource);
        CellularIdentifierDisclosureNotifier notifier = getNotifier();

        // One round of disclosures
        notifier.enable(mContext);
@@ -191,7 +180,7 @@ public class CellularIdentifierDisclosureNotifierTest {
                .setIdentifierDisclosure(any(), eq(SUB_ID_1), eq(2), any(), any());

        // Window close should reset the counter
        executor.advanceTime(WINDOW_CLOSE_ADVANCE_MILLIS);
        mExecutor.advanceTime(WINDOW_CLOSE_ADVANCE_MILLIS);
        assertEquals(0, notifier.getCurrentDisclosureCount(SUB_ID_1));

        // A new disclosure should increment as normal
@@ -203,10 +192,7 @@ public class CellularIdentifierDisclosureNotifierTest {

    @Test
    public void testDisableClosesWindow() {
        TestExecutorService executor = new TestExecutorService();
        CellularIdentifierDisclosureNotifier notifier =
                new CellularIdentifierDisclosureNotifier(
                        executor, 15, TimeUnit.MINUTES, mSafetySource);
        CellularIdentifierDisclosureNotifier notifier = getNotifier();

        // One round of disclosures
        notifier.enable(mContext);
@@ -231,10 +217,7 @@ public class CellularIdentifierDisclosureNotifierTest {

    @Test
    public void testMultipleSubIdsTrackedIndependently() {
        TestExecutorService executor = new TestExecutorService();
        CellularIdentifierDisclosureNotifier notifier =
                new CellularIdentifierDisclosureNotifier(
                        executor, 15, TimeUnit.MINUTES, mSafetySource);
        CellularIdentifierDisclosureNotifier notifier = getNotifier();

        notifier.enable(mContext);
        for (int i = 0; i < 3; i++) {
@@ -262,4 +245,27 @@ public class CellularIdentifierDisclosureNotifierTest {
        assertEquals(3, notifier.getCurrentDisclosureCount(SUB_ID_1));
        assertEquals(4, notifier.getCurrentDisclosureCount(SUB_ID_2));
    }

    @Test
    public void testLogDisclsoure() {
        String mcc = "100";
        String mnc = "200";

        CellularIdentifierDisclosureNotifier notifier = getNotifier();
        SubscriptionInfoInternal subInfoMock = mock(SubscriptionInfoInternal.class);
        when(mSubscriptionManagerService.getSubscriptionInfoInternal(SUB_ID_1)).thenReturn(
                subInfoMock);
        when(subInfoMock.getMcc()).thenReturn(mcc);
        when(subInfoMock.getMnc()).thenReturn(mnc);

        notifier.addDisclosure(mContext, SUB_ID_1, mDislosure);

        verify(mStats, times(1)).logIdentifierDisclosure(mDislosure, mcc, mnc,
                mDislosure.isEmergency());
    }

    private CellularIdentifierDisclosureNotifier getNotifier() {
        return new CellularIdentifierDisclosureNotifier(mExecutor, 15, TimeUnit.MINUTES,
                mSafetySource, mSubscriptionManagerService, mStats);
    }
}