CVE-2025-48564 AI-generated analysis
platform/frameworks/base (core: IntentForwarderActivity, PackageManager/ApplicationPackageManager) and platform/packages/modules/IntentResolver (IntentForwarderActivity, cross-profile intents checker)
IntentForwarderActivity now resolves the forwarded intent's MIME type once and passes that single string to both the IPackageManager.canForwardTo check and a new hidden PackageManager.resolveActivityAsUser(Intent, String resolvedType, flags, userId) overload, so the type used for the cross-profile check is the same as the type used for target resolution; the frameworks/base copy also uses getLaunchedFromPackage() for the privileged skip-consent check, and both copies sanitize the intent selector on the resolver path.
Root cause
In IntentForwarderActivity.onCreate, the forwarding decision and the target resolution each derived the intent's type on their own. canForwardInner called intent.resolveTypeIfNeeded(contentResolver) and passed the result to IPackageManager.canForwardTo, which per index search enforces INTERACT_ACROSS_USERS_FULL in ComputerEngine.canForwardTo. After that, mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, targetUserId) went through ApplicationPackageManager.resolveActivityAsUser, which called intent.resolveTypeIfNeeded(mContext.getContentResolver()) again before mPM.resolveIntent. The commit message states that 'allowing the intent to declare its own type can lead to a security vulnerability if the intent changes its type after the IntentForwarder#canForward check'. Two related gaps were fixed in the same change: the privileged EXTRA_SKIP_USER_CONFIRMATION path identified the caller with getCallingPackage() instead of getLaunchedFromPackage(), and on the path that hands intentReceived to the resolver via startActivityAsCaller only the outer intent was sanitized, not its selector.
How the fix works
- frameworks/base core/java/android/content/pm/PackageManager.java: adds two hidden overloads, resolveActivityAsUser(Intent, String resolvedType, int flags, int userId) and resolveActivityAsUser(Intent, String resolvedType, ResolveInfoFlags, int userId). The javadoc says resolvedType is 'specified explicitly when the data type contained in the intent cannot be trusted'; null falls back to Intent.resolveTypeIfNeeded.
- frameworks/base core/java/android/app/ApplicationPackageManager.java: implements the new overloads. The terminal resolveActivityAsUser(Intent, String, ResolveInfoFlags, int) passes resolvedType to mPM.resolveIntent when it is non-null and only calls intent.resolveTypeIfNeeded when it is null. The existing resolveActivity/resolveActivityAsUser overloads now delegate with resolvedType = null.
- frameworks/base core/java/com/android/internal/app/IntentForwarderActivity.java onCreate: computes String resolvedType = intentReceived.resolveTypeIfNeeded(getContentResolver()) once, passes it to canForward(...) and then to the new mInjector.resolveActivityAsUser(newIntent, resolvedType, MATCH_DEFAULT_ONLY, targetUserId). The last parameter of canForward and canForwardInner changes from ContentResolver to String resolvedType, and they no longer call resolveTypeIfNeeded. The same resolvedType is used for the root intent check and the selector canForwardTo check. Injector and InjectorImpl gain the matching resolveActivityAsUser(Intent, String, int, int) method.
- frameworks/base IntentForwarderActivity.java (same commit): the privileged-caller skip-user-consent path now uses getLaunchedFromPackage() instead of getCallingPackage(). On the resolver launch path, after sanitizeIntent(intentReceived), the selector is also sanitized when intentReceived.getSelector() != null, before startActivityAsCaller.
- frameworks/base core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java CrossProfileIntentsChecker.hasCrossProfileIntents: adapted to the new canForward signature by passing intent.resolveTypeIfNeeded(mContentResolver).
- packages/modules/IntentResolver java/src/com/android/intentresolver/IntentForwarderActivity.java and AbstractMultiProfilePagerAdapter.java (commit 000c3577880a): the module copy of the forwarder gets the single resolvedType, canForward/canForwardInner taking a String, an Injector overload that calls the new PackageManager resolveActivityAsUser(intent, resolvedType, flags, userId), and selector sanitization on the resolver path. This commit does not contain the getLaunchedFromPackage() change. It calls the overload added by the frameworks/base commit.
- frameworks/base commit 2fadc52d7f18 (r29) is in effect test-only. It makes the executor and the DEVICE_PROVISIONED check injectable (Injector.getExecutorService/isDeviceProvisioned, whose production implementations keep the previous behaviour), adds HIDE_OVERLAY_WINDOWS to the coretests manifest, and corrects the backwards assertNull assertions added to IntentForwarderActivityTest by the first commit.
Completeness
- Covers the forwarder's own two consumers of the type: canForwardTo (root intent and selector) and the follow-up resolveActivityAsUser now share one resolvedType string captured in onCreate.
- The selector check in canForwardInner now receives the root intent's resolvedType rather than a type resolved from the selector itself. The material does not show whether a selector can carry its own data or type that would need separate handling.
- AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker still resolves the type at call time for each intent. This is an adaptation to the new signature.
- The fix does not change the final launch (startActivityAsCaller of newIntent after the resolve future completes). The material does not show whether the standard activity-start path derives the type again independently.
- The existing resolveActivity/resolveActivityAsUser overloads still resolve the type from the intent; only callers that use the new overload get the single-resolution behaviour.
- The first commit's test assertions were inverted (assertNull on captured intents that had to be non-null); they were only corrected in the r29 test commit.
Behavior changes
- New hidden PackageManager overloads resolveActivityAsUser(Intent, String, int, int) and (Intent, String, ResolveInfoFlags, int). A PackageManager subclass that does not override the ResolveInfoFlags variant throws UnsupportedOperationException when it is called.
- IntentForwarderActivity.canForward signature changed (ContentResolver replaced by String resolvedType) in both frameworks/base and the IntentResolver module. The Injector interface gains a new method, and after r29 in frameworks/base also getExecutorService/isDeviceProvisioned.
- In frameworks/base, the privileged EXTRA_SKIP_USER_CONFIRMATION handling now uses getLaunchedFromPackage() rather than getCallingPackage().
- On the resolver hand-off path, intent selectors are now passed through sanitizeIntent as well as the outer intent.
- The forwarder's resolve for the target user uses the type computed at check time instead of deriving it again.
Per major
- Android 14: Same fix: frameworks/base 042b52a8551f (API addition, IntentForwarderActivity, AbstractMultiProfilePagerAdapter, tests) and IntentResolver 000c3577880a at android-security-14.0.0_r24; test/injector follow-up frameworks/base 2fadc52d7f18 at r29. Per-major facts report 6/8 lines of the patched region present at r29.
- Android 15: Same fix: frameworks/base bf9e58d7edb5 at android-security-15.0.0_r12. Its content matches 14 apart from the surrounding private-profile code (buildAndExecuteForPrivateProfile, singleTabOnly). The material does not show an IntentResolver module counterpart on 15. 6/8 lines of the patched region present at r17.
- Android 16: Adapted fix in IntentResolver 4e6cf5285d0b at android-security-16.0.0_r3. It has the same IntentForwarderActivity changes, but the canForward call-site adaptation is in java/src/com/android/intentresolver/emptystate/CrossProfileIntentsChecker.java instead of AbstractMultiProfilePagerAdapter. The material does not show the frameworks/base API-addition commit on 16. 8/8 lines present at r8.
- Android 17: Resolved by change_id at android-security-17.0.0_r1. The only commit shown is the test/injector commit frameworks/base 323c8b968a88, which also re-enables the test in core/tests/coretests/Android.bp. The material does not show the main fix commit. 8/8 lines present at r1.
Unknowns
- The concrete trigger: how the intent's type can change between the canForwardTo check and the resolve (for example, whether a content provider's answer is involved).
- Which of the listed bugs (403565650, 407763772, 397216638) maps to this CVE.
- Whether the frameworks/base API addition and IntentForwarderActivity change exist on the 16 and 17 security branches (only the module fix for 16 and the test commit for 17 are shown).
- Whether the IntentResolver module fix was applied on Android 15.
- Why per-major facts report only 6/8 patched-region lines present on 14 and 15 at the latest tags.
- Whether the final startActivityAsCaller launch path re-resolves the type independently of the forwarder's resolvedType.