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

Commit 8a5e38bb authored by Rambo Wang's avatar Rambo Wang
Browse files

Resist SIM logical channel leaking when clients crash

UiccPort keeps all the opened logical channel record. When detecting
the apps died without closing the channel, UiccPort will proactively
clear them up to avoid leaking.

Bug: 197658986
Test: atest IccOpenLogicalChannelRequestTest CarrierApiTest
Test: Manually crash client and observe the opened channel released
Change-Id: I37ecc55a7b886d10b84a75054c237bbc05f925c4
Merged-In: I37ecc55a7b886d10b84a75054c237bbc05f925c4
(cherry picked from commit 3dc7d108)
parent 8d8f7ee8
Loading
Loading
Loading
Loading
+100 −0
Original line number Diff line number Diff line
@@ -16,19 +16,27 @@

package com.android.internal.telephony.uicc;

import android.annotation.NonNull;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.telephony.TelephonyManager;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.IccLogicalChannelRequest;
import com.android.internal.telephony.TelephonyComponentFactory;
import com.android.telephony.Rlog;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

public class UiccPort {
    protected static final String LOG_TAG = "UiccPort";
@@ -48,6 +56,11 @@ public class UiccPort {
    private int mPortIdx;
    private int mPhysicalSlotIndex;

    // The list of the opened logical channel record. The channels will be closed by us when
    // detecting client died without closing them in advance.
    @GuardedBy("mOpenChannelRecords")
    private final List<OpenLogicalChannelRecord> mOpenChannelRecords = new ArrayList<>();

    public UiccPort(Context c, CommandsInterface ci, IccCardStatus ics, int phoneId, Object lock,
            UiccCard uiccCard) {
        if (DBG) log("Creating");
@@ -449,9 +462,96 @@ public class UiccPort {
        pw.println(" mIccid=" + mIccid);
        pw.println(" mPhoneId=" + mPhoneId);
        pw.println(" mPhysicalSlotIndex=" + mPhysicalSlotIndex);
        synchronized (mOpenChannelRecords) {
            pw.println(" mOpenChannelRecords=" + mOpenChannelRecords);
        }
        pw.println();
        if (mUiccProfile != null) {
            mUiccProfile.dump(fd, pw, args);
        }
    }

    /**
     * Informed that a logical channel has been successfully opened.
     *
     * @param request the original request to open the channel, with channel id attached.
     * @hide
     */
    public void onLogicalChannelOpened(@NonNull IccLogicalChannelRequest request) {
        OpenLogicalChannelRecord record = new OpenLogicalChannelRecord(request);
        try {
            request.binder.linkToDeath(record, /*flags=*/ 0);
            addOpenLogicalChannelRecord(record);
            if (DBG) log("onLogicalChannelOpened: monitoring client " + record);
        } catch (RemoteException | NullPointerException ex) {
            loge("IccOpenLogicChannel client has died, clean up manually");
            record.binderDied();
        }
    }

    /**
     * Informed that a logical channel has been successfully closed.
     *
     * @param channelId the channel id of the logical channel that was just closed.
     * @hide
     */
    public void onLogicalChannelClosed(int channelId) {
        OpenLogicalChannelRecord record = getOpenLogicalChannelRecord(channelId);
        if (record != null && record.mRequest != null && record.mRequest.binder != null) {
            if (DBG) log("onLogicalChannelClosed: stop monitoring client " + record);
            record.mRequest.binder.unlinkToDeath(record, /*flags=*/ 0);
            removeOpenLogicalChannelRecord(record);
            record.mRequest.binder = null;
        }
    }

    /** Get the OpenLogicalChannelRecord matching the channel id. */
    @VisibleForTesting
    public OpenLogicalChannelRecord getOpenLogicalChannelRecord(int channelId) {
        synchronized (mOpenChannelRecords) {
            for (OpenLogicalChannelRecord channelRecord : mOpenChannelRecords) {
                if (channelRecord.mRequest != null
                        && channelRecord.mRequest.channel == channelId) {
                    return channelRecord;
                }
            }
        }
        return null;
    }

    private void addOpenLogicalChannelRecord(OpenLogicalChannelRecord record) {
        synchronized (mOpenChannelRecords) {
            mOpenChannelRecords.add(record);
        }
    }

    private void removeOpenLogicalChannelRecord(OpenLogicalChannelRecord record) {
        synchronized (mOpenChannelRecords) {
            mOpenChannelRecords.remove(record);
        }
    }

    /** Record to keep open logical channel info. */
    @VisibleForTesting
    public class OpenLogicalChannelRecord implements IBinder.DeathRecipient {
        IccLogicalChannelRequest mRequest;

        OpenLogicalChannelRecord(IccLogicalChannelRequest request) {
            this.mRequest = request;
        }

        @Override
        public void binderDied() {
            loge("IccOpenLogicalChannelRecord: client died, close channel in record " + this);
            iccCloseLogicalChannel(mRequest.channel, /* response= */ null);
            onLogicalChannelClosed(mRequest.channel);
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder("OpenLogicalChannelRecord {");
            sb.append(" mRequest=" + mRequest).append("}");
            return sb.toString();
        }
    }
}
+63 −0
Original line number Diff line number Diff line
@@ -15,15 +15,21 @@
 */
package com.android.internal.telephony.uicc;

import static com.google.common.truth.Truth.assertThat;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.verify;

import android.os.Binder;
import android.telephony.TelephonyManager;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;

import com.android.internal.telephony.IccLogicalChannelRequest;
import com.android.internal.telephony.TelephonyTest;

import org.junit.After;
@@ -35,6 +41,9 @@ import org.mockito.Mock;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class UiccPortTest extends TelephonyTest {

    private static final int CHANNEL_ID = 1;

    @Mock
    private UiccCard mUiccCard;
    private UiccPort mUiccPort;
@@ -86,4 +95,58 @@ public class UiccPortTest extends TelephonyTest {
        assertEquals(mPhoneId, mUiccPort.getPhoneId());
        assertEquals(TelephonyManager.DEFAULT_PORT_INDEX, mUiccPort.getPortIdx());
    }

    @Test
    @SmallTest
    public void testGetOpenLogicalChannelRecord_noChannelOpened_shouldReturnNull() {
        assertThat(mUiccPort.getOpenLogicalChannelRecord(CHANNEL_ID)).isNull();
    }

    @Test
    @SmallTest
    public void testOnLogicalChannelOpened_withChannelOpen_recordShouldMatch() {
        IccLogicalChannelRequest request = getIccLogicalChannelRequest();

        mUiccPort.onLogicalChannelOpened(request);

        UiccPort.OpenLogicalChannelRecord record = mUiccPort.getOpenLogicalChannelRecord(
                CHANNEL_ID);
        assertThat(record).isNotNull();
    }

    @Test
    @SmallTest
    public void testOnOpenLogicalChannelClosed_withChannelOpenThenClose_noRecordLeft() {
        IccLogicalChannelRequest request = getIccLogicalChannelRequest();

        mUiccPort.onLogicalChannelOpened(request);
        mUiccPort.onLogicalChannelClosed(CHANNEL_ID);

        UiccPort.OpenLogicalChannelRecord record = mUiccPort.getOpenLogicalChannelRecord(
                CHANNEL_ID);
        assertThat(record).isNull();
    }

    @Test
    @SmallTest
    public void testClientDied_withChannelOpened_shouldGetCleanup() {
        IccLogicalChannelRequest request = getIccLogicalChannelRequest();
        mUiccPort.onLogicalChannelOpened(request);

        UiccPort.OpenLogicalChannelRecord record = mUiccPort.getOpenLogicalChannelRecord(
                CHANNEL_ID);
        record.binderDied();

        record = mUiccPort.getOpenLogicalChannelRecord(CHANNEL_ID);
        assertThat(record).isNull();
        verify(mUiccProfile).iccCloseLogicalChannel(eq(CHANNEL_ID), eq(null));
    }

    private IccLogicalChannelRequest getIccLogicalChannelRequest() {
        IccLogicalChannelRequest request = new IccLogicalChannelRequest();
        request.channel = CHANNEL_ID;
        request.subId = 0;
        request.binder = new Binder();
        return request;
    }
}