CVE-2025-48566 AI-generated analysis
platform/frameworks/base, cross-profile intent forwarding (com.android.internal.app.IntentForwarderActivity, android.content.pm.PackageManager / android.app.ApplicationPackageManager)
IntentForwarderActivity now resolves the received intent's MIME type once and passes that same string both to the IPackageManager.canForwardTo check and to a new PackageManager.resolveActivityAsUser(Intent, String resolvedType, ...) overload. It also sanitizes the received intent's selector and uses getLaunchedFromPackage() instead of getCallingPackage() for the privileged skip-consent check.
Root cause
In core/java/com/android/internal/app/IntentForwarderActivity.java, canForwardInner computed resolvedType itself with intent.resolveTypeIfNeeded(contentResolver). The later mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, targetUserId) went through ApplicationPackageManager.resolveActivityAsUser, which called intent.resolveTypeIfNeeded(mContext.getContentResolver()) again. So the type was derived from the intent twice. Per the commit message, an intent that changes its type after the IntentForwarder#canForward check is checked against one type and resolved with another. The same class had two more issues. First, the path that forwards intentReceived to the resolver (the sanitizeIntent(intentReceived) block before startActivityAsCaller) sanitized only the top-level intent and not intentReceived.getSelector(). Second, the privileged skip-user-consent decision used getCallingPackage() rather than getLaunchedFromPackage().
How the fix works
- Commit 042b52a8551f (android-security-14.0.0_r24), core/java/android/content/pm/PackageManager.java: adds two @hide overloads, resolveActivityAsUser(Intent, String resolvedType, int flags, int userId) and resolveActivityAsUser(Intent, String resolvedType, ResolveInfoFlags, int userId). The ResolveInfoFlags variant throws UnsupportedOperationException unless a subclass overrides it. The Javadoc says resolvedType is 'specified explicitly when the data type contained in the intent cannot be trusted'.
- core/java/android/app/ApplicationPackageManager.java: implements the new overloads. resolveActivityAsUser(Intent, String, ResolveInfoFlags, int) passes resolvedType to mPM.resolveIntent when it is non-null and falls back to intent.resolveTypeIfNeeded(...) only when it is null. The existing overloads now delegate with resolvedType = null, so they behave as before.
- IntentForwarderActivity.onCreate: computes String resolvedType = intentReceived.resolveTypeIfNeeded(getContentResolver()) once. It passes this value to canForward(...) and then to mInjector.resolveActivityAsUser(newIntent, resolvedType, MATCH_DEFAULT_ONLY, targetUserId), so the check and the resolution use the same type string.
- IntentForwarderActivity.canForward / canForwardInner: the ContentResolver parameter is replaced by String resolvedType, and the local resolveTypeIfNeeded call in canForwardInner is removed. The same resolvedType is now used for both the root forwardIntent check and the forwardIntent.getSelector() check. Before, the selector's type was resolved from the selector itself.
- IntentForwarderActivity (resolver-forwarding block before startActivityAsCaller): after sanitizeIntent(intentReceived), it also calls sanitizeIntent(intentReceived.getSelector()) when a selector is present. The updated test checks the intents passed to canForwardTo (root and selector), not this path.
- IntentForwarderActivity (managed-profile skip-consent logic): String callingPackage = getLaunchedFromPackage() replaces getCallingPackage().
- IntentForwarderActivity.Injector / InjectorImpl: adds resolveActivityAsUser(Intent, String resolvedType, int flags, int userId), which calls getPackageManager().resolveActivityAsUser(intent, resolvedType, flags, userId) through CompletableFuture.supplyAsync.
- core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java, CrossProfileIntentsChecker.hasCrossProfileIntents: updated for the new canForward signature by passing intent.resolveTypeIfNeeded(mContentResolver). The root intent's type is now also used for the selector check.
- Follow-up commit 2fadc52d7f18 (android-security-14.0.0_r29, 'Fix & speed up IntentForwarderActivityTest') is mainly about the test. In IntentForwarderActivity it adds Injector.getExecutorService() and Injector.isDeviceProvisioned(); InjectorImpl keeps Executors.newSingleThreadExecutor() and the Settings.Global.DEVICE_PROVISIONED read. In the test it fixes the backwards assertNull assertions from the first commit's test and removes a 30 s awaitTermination from the test wrapper activity. It also adds HIDE_OVERLAY_WINDOWS to core/tests/coretests/AndroidManifest.xml. It does not change the forwarding checks.
Completeness
- Covered: one type resolution shared between IPackageManager.canForwardTo (root intent and selector) and the ResolveInfo lookup in IntentForwarderActivity; selector sanitization on the intentReceived path; caller identity for the skip-consent path.
- Only IntentForwarderActivity uses the new resolvedType overloads. All other PackageManager.resolveActivityAsUser callers still derive the type from the intent (null fallback in ApplicationPackageManager).
- The selector check in canForward reuses the root intent's resolvedType. The material does not show whether a selector can carry its own data or type that would differ.
- The diff does not show the actual launch (startActivityAsCaller) after the check, so it is not visible whether the system-side start path re-resolves the type independently.
- The first commit's test contained inverted assertions (assertNull on a non-null captured Intent), which only the follow-up commit corrects. The follow-up's message says it re-enables the test; on 17 it removes the test exclusion from core/tests/coretests/Android.bp.
Behavior changes
- New @hide methods PackageManager.resolveActivityAsUser(Intent, String, int, int) and (Intent, String, ResolveInfoFlags, int). The ResolveInfoFlags variant throws UnsupportedOperationException in PackageManager subclasses that do not override it.
- IntentForwarderActivity resolves the intent type once in onCreate. canForwardTo and resolveIntent now receive the same string instead of types derived separately; the selector is checked with the root intent's type.
- The selector of the intent forwarded to the resolver is now passed through sanitizeIntent (its exact effect is not shown in the material).
- The privileged skip-user-confirmation check now uses getLaunchedFromPackage() instead of getCallingPackage().
- Follow-up commit: the executor and device-provisioned lookups go through the Injector, with the same defaults in production.
Per major
- Android 14: Same fix: 042b52a8551f in android-security-14.0.0_r24 (main fix), plus the test/injector follow-up 2fadc52d7f18 in android-security-14.0.0_r29. Per-major facts report 6/8 lines of the patched region present at r29; the material does not explain the 2 missing lines.
- Android 15: Adapted fix: bf9e58d7edb5 in android-security-15.0.0_r12. Same logic with context differences (an isPrivateProfile branch after resolveActivityAsUser, a singleTabOnly branch after the selector sanitization). 6/8 lines present at android-security-15.0.0_r17.
- Android 16: Adapted fix: 28579dff4305 in android-security-16.0.0_r3, same content as 15 with shifted line numbers. 6/8 lines present at android-security-16.0.0_r8.
- Android 17: Resolved via change_id with 323c8b968a88 ('Fix & speed up IntentForwarderActivityTest') in android-security-17.0.0_r1. That commit also removes the test exclusion in core/tests/coretests/Android.bp. 8/8 lines of the patched region are present, and the context of the test diff already contains the capturedIntents assertions, which suggests the main fix is already in the 17 baseline.
Unknowns
- The body of IntentForwarderActivity.sanitizeIntent is not shown.
- It is not visible whether the system-side activity start after the check (startActivityAsCaller) re-resolves the type independently.
- The trigger details behind bugs 403565650, 407763772 and 397216638 are not shown, nor which of the three sub-fixes (type TOCTOU, selector sanitization, launched-from package) accounts for the CVE's High EoP rating.
- Why only 6/8 lines of the patched region are present at the latest 14, 15 and 16 security tags.
- Whether reusing the root intent's type for the selector check is intended, and whether it affects legitimate selector intents.