CVE-2024-40653 AI-generated analysis
platform/packages/services/Telecomm, ConnectionServiceWrapper (ConnectionService binding / call creation)
Telecom's ConnectionServiceWrapper now schedules a 15 second timeout when it asks a bound ConnectionService to create a connection or conference. If the app never completes creation, the request is failed with DisconnectCause.ERROR. Per the commit message, this lets Telecom unbind the service instead of keeping it bound indefinitely.
Root cause
ConnectionServiceWrapper.createConnection() and createConference() send createConnection/createConference to the app's ConnectionService over mServiceInterface. They then wait for the app to call back handleCreateConnectionComplete/handleCreateConferenceComplete on the Adapter. That callback had no deadline, so if the app never created the connection the binding was never released. Per the commit message, this let a malicious app keep a declared permission forever, even in the background.
How the fix works
- Commit b007692a8d52 (android-security-14.0.0_r12, 'DO NOT MERGE' first iteration) changes src/com/android/server/telecom/ConnectionServiceWrapper.java. It adds SERVICE_BINDING_TIMEOUT = 15000L, a single-thread ScheduledExecutorService mScheduledExecutor, and a Map<Call, ScheduledFuture<?>> mScheduledFutureMap (ConcurrentHashMap).
- ConnectionServiceWrapper.createConnection() and createConference(): just before calling mServiceInterface.createConnection(...)/createConference(...), the fix builds an android.telecom.Logging.Runnable('CSW.cC', mLock). Its loggedRun() checks call.isCreateConnectionComplete(). If that is false, it logs an error with the component name, adds a CREATE_CONNECTION_TIMEOUT / CREATE_CONFERENCE_TIMEOUT call event, and calls response.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.ERROR)) (or handleCreateConferenceFailure). The runnable is scheduled on mScheduledExecutor after SERVICE_BINDING_TIMEOUT ms, and the ScheduledFuture is stored in mScheduledFutureMap keyed by the Call.
- ConnectionServiceWrapper.Adapter.handleCreateConnectionComplete() and handleCreateConferenceComplete(): look up the Call via mCallIdMapper.getCall(callId). If mScheduledFutureMap contains it, cancel the pending future and remove the entry, so a timely creation disarms the timeout.
- src/com/android/server/telecom/LogUtils.java: adds Events.CREATE_CONNECTION_TIMEOUT and Events.CREATE_CONFERENCE_TIMEOUT. Call.getState() and ConnectionServiceWrapper.addCall() are marked @VisibleForTesting (addCall is also made public), and setScheduledExecutorService() is added for tests.
- Commit 12016109c473 (android-security-14.0.0_r17, second iteration, Change-Id If46cfa26...) re-applies the same logic with hardening. In both Adapter handlers, the lookup is guarded by 'call != null &&' before mScheduledFutureMap.containsKey(call). The commit message says the first iteration caused an NPE.
- Second iteration, ConnectionServiceWrapper.removeServiceInterface(): returns early with a warning when mServiceInterface is already null (duplicate calls from onBinderDied and onServiceDisconnected). After mServiceInterface = null, it shuts down mScheduledExecutor and sets it to null. handleConnectionServiceDeath() now clears mScheduledFutureMap alongside mCallIdMapper.clear().
- Second iteration also adds anomaly reporting: CREATE_CONNECTION_TIMEOUT_ERROR_UUID / CREATE_CONFERENCE_TIMEOUT_ERROR_UUID with messages, reported via mAnomalyReporter (AnomalyReporterAdapterImpl) inside the timeout runnables, plus setAnomalyReporterAdapter() for tests.
- Tests: CallsManagerTest.testConnectionServiceCreateConnectionTimeout registers a mock IConnectionService via ComponentContextFixture.addConnectionService and injects a TestScheduledExecutorService. It then calls service.createConnection(call, response) and asserts that a runnable is scheduled at 15000 ms. The second iteration also advances time and verifies response.handleCreateConnectionFailure(DisconnectCause.ERROR). BasicCallTests calls call.setIsCreateConnectionComplete(true) in two tests, and ComponentContextFixture gains removeConnectionService()/removeService().
Completeness
- Covers both outbound creation paths in ConnectionServiceWrapper: createConnection() and createConference(). The timeout is disarmed only by Adapter.handleCreateConnectionComplete()/handleCreateConferenceComplete().
- The timeout is keyed per Call in mScheduledFutureMap. A second createConnection() for the same Call overwrites the map entry without cancelling the earlier future; the earlier runnable still only acts if call.isCreateConnectionComplete() is false.
- In the second iteration, removeServiceInterface() shuts down mScheduledExecutor and sets it to null, but createConnection()/createConference() call mScheduledExecutor.schedule() without a null check. The material does not show whether a wrapper instance is reused for a later bind after service death.
- The first iteration (b007692a8d52) lacks the null check on the Call returned by mCallIdMapper.getCall(callId), and it lacks the executor shutdown and map clearing. The second iteration (12016109c473) supersedes it. This diff does not touch other reasons the wrapper binds to a ConnectionService (outside connection/conference creation), and the material does not show those paths.
Behavior changes
- If a ConnectionService does not complete connection/conference creation within 15 seconds of the request, the request is now failed with DisconnectCause.ERROR via CreateConnectionResponse.handleCreateConnectionFailure / handleCreateConferenceFailure. Legitimately slow services are affected the same way.
- New call log events CREATE_CONNECTION_TIMEOUT and CREATE_CONFERENCE_TIMEOUT, and an error log with a stack trace naming the component. The second iteration also adds anomaly reports with fixed UUIDs 54b7203d-... and caafe5ea-....
- Second iteration: removeServiceInterface() is idempotent (early return when mServiceInterface is null) and shuts down the wrapper's scheduled executor. handleConnectionServiceDeath() clears pending timeout futures.
- Each ConnectionServiceWrapper instance now creates its own single-thread ScheduledExecutorService. The timeout runnable is constructed with mLock.
Per major
- Android 14: Fixed in two steps. First b007692a8d52 ('DO NOT MERGE' first iteration, android-security-14.0.0_r12). Then 12016109c473 (android-security-14.0.0_r17), which re-applies the change with the null check on the looked-up Call, idempotent removeServiceInterface(), executor shutdown, mScheduledFutureMap.clear() and anomaly reporting. At android-security-14.0.0_r29 the patched region is fully present (8/8 lines).
- Android 15: Same fix as the Android 14 second iteration: 4f73f974ca16 in android-security-15.0.0_r5, with the same ConnectionServiceWrapper and LogUtils changes. The test's ConnectionServiceWrapper constructor call takes an extra mFeatureFlags argument. At android-security-15.0.0_r17 the patched region is fully present (8/8 lines).
- Android 16: Not affected per the bulletin. The patched region is present at android-security-16.0.0_r8 (8/8 lines).
- Android 17: Not affected per the bulletin. The patched region is present at android-security-17.0.0_r1 (8/8 lines).
Unknowns
- The commit message describes the privilege the attacker retained (the 'declared permission' / process state) only generically.
- The material does not show whether a ConnectionServiceWrapper instance can be reused for a new bind after removeServiceInterface() nulls mScheduledExecutor (potential NPE in createConnection).
- The material does not show whether other bind triggers in ConnectionServiceWrapper, besides createConnection/createConference, can keep a service bound without a deadline.
- The callers that drive ConnectionServiceWrapper.createConnection()/createConference() during call setup are not shown in the material.
- The material does not explain why the first iteration was marked DO NOT MERGE or exactly how it was superseded in the 14 security branch.