CVE-2026-58874 AI-generated analysis
platform/frameworks/opt/telephony (SmsController, IccSmsInterfaceManager) and platform/frameworks/base (telephony/common TelephonyPermissions)
Adds the missing subscription-to-user association check to SmsController.sendStoredText and sendStoredMultipartText (frameworks/opt/telephony). When the calling user is not associated with the subscription, the send is rejected with RESULT_USER_NOT_ALLOWED. The fix also makes the destAddr parameter of TelephonyPermissions.checkSubscriptionAssociatedWithUser nullable (frameworks/base) so the new callers can pass a null address.
Root cause
Before the fix, SmsController.sendStoredText and SmsController.sendStoredMultipartText forwarded to IccSmsInterfaceManager.sendStoredText / sendStoredMultipartText for the given subId without calling TelephonyPermissions.checkSubscriptionAssociatedWithUser. sendTextForSubscriber is different: its existing SmsControllerTest cases show it already consults SubscriptionManager.isSubscriptionAssociatedWithUser (per index search). As a result, a caller running as one user could request a stored-message send over a subscription that is not associated with that user. Separately, the 4-argument checkSubscriptionAssociatedWithUser declared destAddr @NonNull and passed it straight to TelephonyManager.isEmergencyNumber, with no handling for callers that cannot resolve a destination address.
How the fix works
- frameworks/base, telephony/common/com/android/internal/telephony/TelephonyPermissions.java:checkSubscriptionAssociatedWithUser(Context,int,UserHandle,String): the destAddr annotation changes from @NonNull to @Nullable, and the emergency-number short-circuit becomes `tm != null && destAddr != null && tm.isEmergencyNumber(destAddr)`. With a null address the method skips the emergency bypass and falls through to the 3-argument checkSubscriptionAssociatedWithUser(context, subId, callerUserHandle).
- frameworks/opt/telephony, src/java/com/android/internal/telephony/IccSmsInterfaceManager.java:loadTextAndAddress: visibility changes from private to public so SmsController can read the stored message body and address (index 1) for a message URI. The method clears calling identity around the ContentResolver query and returns null if no row is found or on SQLiteException.
- frameworks/opt/telephony, src/java/com/android/internal/telephony/SmsController.java: new private helper getDestAddress(IccSmsInterfaceManager, Uri). It returns null when iccSmsIntMgr is null or loadTextAndAddress returns null, and otherwise returns textAndAddress[1].
- SmsController.java:sendStoredText: after the caller log line, the method gets callingUser = Binder.getCallingUserHandle(), uid = Binder.getCallingUid() and destAddr = getDestAddress(...), then calls TelephonyPermissions.checkSubscriptionAssociatedWithUser(mContext, subId, callingUser, destAddr). If that returns false, it calls TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(mContext, subId, uid, callingPkg), logs, sends SmsManager.RESULT_USER_NOT_ALLOWED through sendErrorInPendingIntent(sentIntent, ...) and returns before touching iccSmsIntMgr.
- SmsController.java:sendStoredMultipartText: the same gate, using sendErrorInPendingIntents(sentIntents, SmsManager.RESULT_USER_NOT_ALLOWED).
- tests/telephonytests/.../SmsControllerTest.java: four new tests (sendStoredText_ and sendStoredMultipartText_, each with associatedUser_sendsSms and notAssociatedUser_fails). They stub loadTextAndAddress and SubscriptionManager.isSubscriptionAssociatedWithUser, then verify whether IccSmsInterfaceManager.sendStoredText or sendStoredMultipartText is invoked.
Completeness
- Both stored-message entry points in SmsController (sendStoredText and sendStoredMultipartText) get the gate, so the single-message and multipart paths are handled the same way.
- The gate also runs when iccSmsIntMgr is null: destAddr becomes null and the 3-argument association check still executes. The existing null-iccSmsIntMgr error path after it is unchanged.
- The association check is skipped when the stored destination address is an emergency number, because of the existing emergency short-circuit in TelephonyPermissions. This follows the existing semantics of the 4-argument helper.
- The destination address used for the emergency decision is read from the stored message (through the message URI), not taken from a caller-supplied argument.
- This commit does not touch other SmsController entry points. Existing tests in the index show that sendTextForSubscriber already had an association check. The material does not show the state of the remaining methods.
Behavior changes
- Callers of sendStoredText / sendStoredMultipartText whose user is not associated with the subscription now get SmsManager.RESULT_USER_NOT_ALLOWED in their sentIntent(s), and the message is not sent.
- TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate may now be invoked for these two calls.
- Each call now runs an extra SMS provider query (loadTextAndAddress, under cleared calling identity) before the send, plus a TelephonyManager.isEmergencyNumber lookup when an address is found.
- IccSmsInterfaceManager.loadTextAndAddress is now public (internal, non-SDK surface).
- The 4-argument TelephonyPermissions.checkSubscriptionAssociatedWithUser explicitly accepts a null destAddr and treats it as non-emergency.
Per major
- Android 14: Same fix: platform/frameworks/opt/telephony 2c10fa519945, plus platform/frameworks/base 2e8edff2ab34, in android-security-14.0.0_r29. It adds the android.os.UserHandle and ContentResolver imports, inserts the gate in both methods, and adds getDestAddress and four tests. The resolver reports 7/8 lines of the patched region present.
- Android 15: Same fix: platform/frameworks/opt/telephony 3ed5e1fdd5da in android-security-15.0.0_r17. The logic is identical; only the ContentResolver import is added in the diff, and test file offsets differ. The resolver reports 7/8 lines present.
- Android 16: Adapted fix: platform/frameworks/opt/telephony b77a21c16113 in android-security-16.0.0_r8. callingUser already existed in both methods, so only `int uid` is hoisted and reused in the SecurityException message. The gate is inserted after the package check, and the downstream IccSmsInterfaceManager calls pass callingUser.getIdentifier(). The tests use 7-argument signatures. The commit also adds an extra .txt file, SmsController_fixed_methods.txt, containing copies of the two patched methods. The resolver reports 7/8 lines present.
- Android 17: Not affected: the bulletin does not list Android 17. At android-security-17.0.0_r1, 6/8 lines of the patched region are present, and the material identifies no commit.
Unknowns
- Which permission or app-op checks IccSmsInterfaceManager.sendStoredText/sendStoredMultipartText perform downstream, and whether they read the message address again during the send.
- How TelephonyManager.isEmergencyNumber behaves with a null argument in the pre-fix code, which determines whether the base change is required or only defensive.
- Whether SmsController methods other than sendTextForSubscriber and the two patched methods carry the association check at these security tags.
- Whether the frameworks/base nullable change has separate commits on Android 15 and 16; the material lists only the opt/telephony commits for those versions.
- Internal behavior of TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate and of the 3-argument checkSubscriptionAssociatedWithUser, including the rejectBadSubIdInteraction flag behavior seen in TelephonyPermissionsTest (per index search).