CVE-2026-28644 AI-generated analysis
platform/frameworks/base, WindowManager / ActivityTaskManagerService (activity start, startNextMatchingActivity)
ActivityTaskManagerService.startNextMatchingActivity() now forwards the original caller's identity (launchedFromUid/launchedFromPackage/launchedFromFeatureId) to the next resolved activity only when the intermediary activity shares the original caller's app ID or runs as SYSTEM_UID; otherwise the calling UID and package are reset to the intermediary's own.
Root cause
In services/core/java/com/android/server/wm/ActivityTaskManagerService.java, startNextMatchingActivity() always built the ActivityStarter with .setCallingUid(r.launchedFromUid), .setCallingPackage(r.launchedFromPackage) and .setCallingFeatureId(r.launchedFromFeatureId), where r is the ActivityRecord of the activity calling the API. So the activity that received an intent could hand it on to the next matching activity while the system reported the original launcher's UID and package as the caller. Per the commit message, this let an untrusted intermediary trampoline an implicit intent from a privileged app to another app. Permission checks based on the calling identity (the message cites REQUEST_INSTALL_PACKAGES) then saw the privileged original caller instead of the intermediary.
How the fix works
- services/core/java/com/android/server/wm/ActivityTaskManagerService.java:startNextMatchingActivity(): the builder chain is split so the ActivityStarter is kept in a local 'starter' before execute(). setCallingPid(-1), setRealCallingPid(origCallingPid), setRealCallingUid(origCallingUid) and setActivityOptions(options) are unchanged.
- A new check sets intermediaryUid = r.getUid(). If !UserHandle.isSameApp(intermediaryUid, r.launchedFromUid) && UserHandle.getAppId(intermediaryUid) != Process.SYSTEM_UID, the starter gets setCallingUid(intermediaryUid) and setCallingPackage(r.packageName). setCallingFeatureId is not called in this branch, so no feature ID is forwarded.
- Otherwise (same app ID as the original caller, or an intermediary whose app ID is SYSTEM_UID, e.g. ChooserActivity per the commit message) the old behavior is kept: setCallingUid(r.launchedFromUid), setCallingPackage(r.launchedFromPackage), setCallingFeatureId(r.launchedFromFeatureId). Then res = starter.execute().
- core/java/android/app/Activity.java: the Javadoc of startNextMatchingActivity(Intent, Bundle) now says the propagated caller identity reflects the intermediary unless it shares the original caller's UID, and that receivers should validate the immediate caller. This is a documentation change only.
- services/tests/wmtests/.../ActivityTaskManagerServiceTests.java: three new tests (differentAppId_propagatesIntermediary, sameAppId_propagatesOriginalCaller, systemUid_propagatesOriginalCaller). A helper mocks IPackageManager.queryIntentActivities and spies on ActivityStarter to verify setCallingUid/setCallingPackage. services/tests/wmtests/.../WindowTestsBase.java: ActivityBuilder gains setIntent(Intent), so a test can pass a full intent instead of a component-only one.
Completeness
- The fix is limited to the single identity-forwarding site in startNextMatchingActivity(). The real calling UID/PID (origCallingUid/origCallingPid from Binder) and the BAL-related handling referenced for b/230492947 are not touched.
- Trust is decided only by app ID: an intermediary with the same app ID as the original caller is trusted, and any intermediary whose app ID is SYSTEM_UID is trusted regardless of package.
- SYSTEM_UID is the only privileged intermediary on the allowlist. Any other system app that calls startNextMatchingActivity now has the caller identity reset to its own UID and package.
- In the reset branch, launchedFromFeatureId is dropped entirely (setCallingFeatureId is not called) rather than replaced with an intermediary feature ID.
- The check runs in the system server, so it applies to every caller of the public Activity.startNextMatchingActivity API without app changes.
Behavior changes
- When an activity launched by a caller with a different app ID that is not SYSTEM_UID calls startNextMatchingActivity, the calling UID and package passed to ActivityStarter for the next activity are now the intermediary's.
- No calling feature ID is forwarded in that case.
- No new error codes: the API still returns true/false based on START_SUCCESS. Intermediaries with the same app ID or SYSTEM_UID keep the old behavior.
- The public Javadoc for Activity.startNextMatchingActivity(Intent, Bundle) now documents the identity behavior.
Per major
- Android 14: Same fix: commit bb7413247455 in android-security-14.0.0_r29 (cherry-pick, Change-Id I3df4b731bfae6ce6f8896c69ecdd118819057b36). On 14 the builder chain has no setUserId(userId). Patched region fully present (8/8).
- Android 15: Same fix: commit ba9c05f37043 in android-security-15.0.0_r17. The only context difference is that the chain also has .setUserId(userId) before the identity check. Patched region fully present (8/8).
- Android 16: Same fix: commit e95170c6378e in android-security-16.0.0_r8, same shape as 15 (with setUserId(userId)). Patched region fully present (8/8).
- Android 17: Not affected: the bulletin does not list Android 17. At android-security-17.0.0_r1 the patched region is fully present (8/8). The material shows no separate 17 commit.
Unknowns
- The material does not show which target activities or permission checks were affected, beyond the commit message's REQUEST_INSTALL_PACKAGES example.
- The material does not show whether any system intermediaries that are not SYSTEM_UID relied on the old forwarding (possible functional regressions).
- The bulletin's affected-version list includes '17-next', but the per-major facts mark Android 17 as not listed. The material does not explain the difference.