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

Commit 4923eb8e authored by Grant Menke's avatar Grant Menke Committed by mse1969
Browse files

DO NOT MERGE Unbind CS if connection is not created within 15 seconds.

This CL adds a check to ensure that connection creation occurs within 15 seconds after binding to that ConnectionService. If the connection/conference is not created in that timespan, this CL adds logic to manually unbind the ConnectionService at that point in time. This prevents malicious apps from keeping a declared permission in forever even in the background.

Bug: 293458004
Test: manually using the provided apk + atest CallsManagerTest
Flag: EXEMPT Security High/Critical Severity CVE
(cherry picked from commit 7aa55ffc)
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:286781dfcb78d8b5c1a77f2390f5251f01943add)
Merged-In: I30caed1481dff5af2223a8ff589846597cee8229
Change-Id: I30caed1481dff5af2223a8ff589846597cee8229
parent 7ef90cb7
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
@@ -306,6 +306,17 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
    /** The state of the call. */
    private int mState;

    /**
     * Determines whether the {@link ConnectionService} has responded to the initial request to
     * create the connection.
     *
     * {@code false} indicates the {@link Call} has been added to Telecom, but the
     * {@link Connection} has not yet been returned by the associated {@link ConnectionService}.
     * {@code true} indicates the {@link Call} has an associated {@link Connection} reported by the
     * {@link ConnectionService}.
     */
    private boolean mIsCreateConnectionComplete = false;

    /** The handle with which to establish this call. */
    private Uri mHandle;

@@ -797,6 +808,19 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
        return mConnectionService;
    }

    /**
     * @return {@code true} if the connection has been created by the underlying
     * {@link ConnectionService}, {@code false} otherwise.
     */
    public boolean isCreateConnectionComplete() {
        return mIsCreateConnectionComplete;
    }

    @VisibleForTesting
    public void setIsCreateConnectionComplete(boolean isCreateConnectionComplete) {
        mIsCreateConnectionComplete = isCreateConnectionComplete;
    }

    @VisibleForTesting
    public int getState() {
        return mState;
@@ -1638,6 +1662,7 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
            CallIdMapper idMapper,
            ParcelableConnection connection) {
        Log.v(this, "handleCreateConnectionSuccessful %s", connection);
        mIsCreateConnectionComplete = true;
        setTargetPhoneAccount(connection.getPhoneAccount());
        setHandle(connection.getHandle(), connection.getHandlePresentation());
        setCallerDisplayName(
+47 −2
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import android.telecom.ConnectionService;
import android.telecom.DisconnectCause;
import android.telecom.GatewayInfo;
import android.telecom.Log;
import android.telecom.Logging.Runnable;
import android.telecom.Logging.Session;
import android.telecom.ParcelableConference;
import android.telecom.ParcelableConnection;
@@ -57,6 +58,11 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

/**
 * Wrapper for {@link IConnectionService}s, handles binding to {@link IConnectionService} and keeps
@@ -68,6 +74,12 @@ import java.util.concurrent.ConcurrentHashMap;
public class ConnectionServiceWrapper extends ServiceBinder implements
        ConnectionServiceFocusManager.ConnectionServiceFocus {

    private static final long SERVICE_BINDING_TIMEOUT = 15000L;
    private ScheduledExecutorService mScheduledExecutor =
            Executors.newSingleThreadScheduledExecutor();
    // Pre-allocate space for 2 calls; realistically thats all we should ever need (tm)
    private final Map<Call, ScheduledFuture<?>> mScheduledFutureMap = new ConcurrentHashMap<>(2);

    private final class Adapter extends IConnectionServiceAdapter.Stub {

        @Override
@@ -79,6 +91,12 @@ public class ConnectionServiceWrapper extends ServiceBinder implements
            try {
                synchronized (mLock) {
                    logIncoming("handleCreateConnectionComplete %s", callId);
                    Call call = mCallIdMapper.getCall(callId);
                    if (mScheduledFutureMap.containsKey(call)) {
                        ScheduledFuture<?> existingTimeout = mScheduledFutureMap.get(call);
                        existingTimeout.cancel(false /* cancelIfRunning */);
                        mScheduledFutureMap.remove(call);
                    }
                    // Check status hints image for cross user access
                    if (connection.getStatusHints() != null) {
                        Icon icon = connection.getStatusHints().getIcon();
@@ -1027,7 +1045,8 @@ public class ConnectionServiceWrapper extends ServiceBinder implements
     * @param context The context.
     * @param userHandle The {@link UserHandle} to use when binding.
     */
    ConnectionServiceWrapper(
    @VisibleForTesting
    public ConnectionServiceWrapper(
            ComponentName componentName,
            ConnectionServiceRepository connectionServiceRepository,
            PhoneAccountRegistrar phoneAccountRegistrar,
@@ -1129,6 +1148,26 @@ public class ConnectionServiceWrapper extends ServiceBinder implements
                        .setRttPipeToInCall(call.getCsToInCallRttPipeForCs())
                        .build();

                Runnable r = new Runnable("CSW.cC", mLock) {
                            @Override
                            public void loggedRun() {
                                if (!call.isCreateConnectionComplete()) {
                                    Log.e(this, new Exception(),
                                            "Connection %s creation timeout",
                                            getComponentName());
                                    Log.addEvent(call, LogUtils.Events.CREATE_CONNECTION_TIMEOUT,
                                            Log.piiHandle(call.getHandle()) + " via:" +
                                                    getComponentName().getPackageName());
                                    response.handleCreateConnectionFailure(
                                            new DisconnectCause(DisconnectCause.ERROR));
                                }
                            }
                        };
                // Post cleanup to the executor service and cache the future, so we can cancel it if
                // needed.
                ScheduledFuture<?> future = mScheduledExecutor.schedule(r.getRunnableToCancel(),
                        SERVICE_BINDING_TIMEOUT, TimeUnit.MILLISECONDS);
                mScheduledFutureMap.put(call, future);
                try {
                    mServiceInterface.createConnection(
                            call.getConnectionManagerPhoneAccount(),
@@ -1414,7 +1453,8 @@ public class ConnectionServiceWrapper extends ServiceBinder implements
        }
    }

    void addCall(Call call) {
    @VisibleForTesting
    public void addCall(Call call) {
        if (mCallIdMapper.getCallId(call) == null) {
            mCallIdMapper.addCall(call);
        }
@@ -1778,4 +1818,9 @@ public class ConnectionServiceWrapper extends ServiceBinder implements
    private void noRemoteServices(RemoteServiceCallback callback) {
        setRemoteServices(callback, Collections.EMPTY_LIST, Collections.EMPTY_LIST);
    }

    @VisibleForTesting
    public void setScheduledExecutorService(ScheduledExecutorService service) {
        mScheduledExecutor = service;
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -86,6 +86,7 @@ public class LogUtils {
        public static final String STOP_CALL_WAITING_TONE = "STOP_CALL_WAITING_TONE";
        public static final String START_CONNECTION = "START_CONNECTION";
        public static final String CREATE_CONNECTION_FAILED = "CREATE_CONNECTION_FAILED";
        public static final String CREATE_CONNECTION_TIMEOUT = "CREATE_CONNECTION_TIMEOUT";
        public static final String BIND_CS = "BIND_CS";
        public static final String CS_BOUND = "CS_BOUND";
        public static final String CONFERENCE_WITH = "CONF_WITH";
+2 −0
Original line number Diff line number Diff line
@@ -927,6 +927,7 @@ public class BasicCallTests extends TelecomSystemTest {
        call.setTargetPhoneAccount(mPhoneAccountA1.getAccountHandle());
        assert(call.isVideoCallingSupported());
        assertEquals(VideoProfile.STATE_BIDIRECTIONAL, call.getVideoState());
        call.setIsCreateConnectionComplete(true);
    }

    /**
@@ -950,6 +951,7 @@ public class BasicCallTests extends TelecomSystemTest {
        call.setTargetPhoneAccount(mPhoneAccountA2.getAccountHandle());
        assert(!call.isVideoCallingSupported());
        assertEquals(VideoProfile.STATE_AUDIO_ONLY, call.getVideoState());
        call.setIsCreateConnectionComplete(true);
    }

    /**
+60 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import static org.mockito.Mockito.when;

import android.content.ComponentName;
import android.net.Uri;
import android.os.IBinder;
import android.os.SystemClock;
import android.telecom.Connection;
import android.telecom.PhoneAccount;
@@ -44,11 +45,13 @@ import com.android.server.telecom.AsyncRingtonePlayer;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.CallState;
import com.android.internal.telecom.IConnectionService;
import com.android.server.telecom.CallerInfoAsyncQueryFactory;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.ClockProxy;
import com.android.server.telecom.ConnectionServiceFocusManager;
import com.android.server.telecom.ConnectionServiceFocusManager.ConnectionServiceFocusManagerFactory;
import com.android.server.telecom.CreateConnectionResponse;
import com.android.server.telecom.ConnectionServiceWrapper;
import com.android.server.telecom.ContactsAsyncHelper;
import com.android.server.telecom.DefaultDialerCache;
@@ -140,6 +143,7 @@ public class CallsManagerTest extends TelecomTestCase {
    @Mock private InCallController mInCallController;
    @Mock private ConnectionServiceFocusManager mConnectionSvrFocusMgr;
    @Mock private BluetoothStateReceiver mBluetoothStateReceiver;
    @Mock private IConnectionService mIConnectionService;

    private CallsManager mCallsManager;

@@ -190,6 +194,20 @@ public class CallsManagerTest extends TelecomTestCase {
                eq(SIM_1_HANDLE), any())).thenReturn(SIM_1_ACCOUNT);
        when(mPhoneAccountRegistrar.getPhoneAccount(
                eq(SIM_2_HANDLE), any())).thenReturn(SIM_2_ACCOUNT);
        when(mIConnectionService.asBinder()).thenReturn(mock(IBinder.class));

        mComponentContextFixture.addConnectionService(new ComponentName(mContext.getPackageName(),
                mContext.getPackageName().getClass().getName()), mIConnectionService);
    }

     @Override
     @After
     public void tearDown() throws Exception {
        mComponentContextFixture.removeConnectionService(
                new ComponentName(mContext.getPackageName(),
                        mContext.getPackageName().getClass().getName()),
                mock(IConnectionService.class));
         super.tearDown();
     }

    @MediumTest
@@ -684,6 +702,33 @@ public class CallsManagerTest extends TelecomTestCase {
        assertEquals(CallState.ACTIVE, newCall.getState());
    }

 
    @Test
    public void testConnectionServiceCreateConnectionTimeout() throws Exception {
        ConnectionServiceWrapper service = new ConnectionServiceWrapper(new ComponentName(
                mContext.getPackageName(), mContext.getPackageName().getClass().getName()), null,
                mPhoneAccountRegistrar, mCallsManager, mContext, mLock, null);
        TestScheduledExecutorService scheduledExecutorService = new TestScheduledExecutorService();
        service.setScheduledExecutorService(scheduledExecutorService);
        Call call = addSpyCall();
        service.addCall(call);
        when(call.isCreateConnectionComplete()).thenReturn(false);
        CreateConnectionResponse response = mock(CreateConnectionResponse.class);

        service.createConnection(call, response);
        waitUntilConditionIsTrueOrTimeout(new Condition() {
            @Override
            public Object expected() {
                return true;
            }

            @Override
            public Object actual() {
                return scheduledExecutorService.isRunnableScheduledAtTime(15000L);
            }
        }, 5000L, "Expected job failed to schedule");
    }

    private Call addSpyCallWithConnectionService(ConnectionServiceWrapper connSvr) {
        Call call = addSpyCall();
        doReturn(connSvr).when(call).getConnectionService();
@@ -739,4 +784,19 @@ public class CallsManagerTest extends TelecomTestCase {
        when(mPhoneAccountRegistrar.getSimPhoneAccountsOfCurrentUser()).thenReturn(
                new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
    }

    private void waitUntilConditionIsTrueOrTimeout(Condition condition, long timeout,
            String description) throws InterruptedException {
        final long start = System.currentTimeMillis();
        while (!condition.expected().equals(condition.actual())
                && System.currentTimeMillis() - start < timeout) {
            sleep(50);
        }
        assertEquals(description, condition.expected(), condition.actual());
    }

    protected interface Condition {
        Object expected();
        Object actual();
    }
}
Loading