CVE-2026-28620 AI-generated analysis
platform/frameworks/base, WindowManager activity start / UriGrantsManager (Intent Redirection Hardening)
Changes the intent-redirection URI grant logic in ActivityStarter and ActivityStartController so that, when an intent's creator UID differs from the launching UID, URI grants are only issued if both the caller's and the creator's checkGrantUriPermissionFromIntent calls yield grants, instead of using or merging whichever party's grants were available; the NeededUriGrants.merge helper is removed.
Root cause
In the intent redirection hardening path, when an Intent carries a valid IntentCreatorToken whose creator UID differs from the resolved calling UID, the code computed two NeededUriGrants sets: intentGrants for the calling UID and creatorIntentGrants for the creator UID. The pre-fix code combined them permissively: if intentGrants was null it was replaced by creatorIntentGrants, otherwise creatorIntentGrants was merged (union of URIs via NeededUriGrants.merge) into intentGrants. The result was that URI grants could be issued to the target activity based on either party's grant result, so one party's lack of grant authorization did not block the grant. The same pattern existed in three places: ActivityStarter.java under the contentUriPermissionApis flag, ActivityStarter.java in the non-flag else branch, and the multi-intent loop in ActivityStartController.java.
How the fix works
- services/core/java/com/android/server/wm/ActivityStarter.java (flagged branch, android.security.Flags.contentUriPermissionApis() true, around line 650): after computing creatorIntentGrants for intentCreatorUid, replaces 'if (intentGrants == null) intentGrants = creatorIntentGrants; else intentGrants.merge(creatorIntentGrants);' with 'if (intentGrants == null || creatorIntentGrants == null) intentGrants = null;'. Only the caller's intentGrants are used, and only when the creator check also returned a non-null set; otherwise no grants are made.
- services/core/java/com/android/server/wm/ActivityStarter.java (non-flag else branch, guarded by 'intentCreatorUid != DEFAULT_INTENT_CREATOR_UID && intentGrants != null', around line 676): same replacement, so a null creatorIntentGrants now clears intentGrants instead of leaving the caller's grants intact.
- services/core/java/com/android/server/wm/ActivityStartController.java (loop over intents[] in the multi-activity start path, around line 497): same replacement after computing creatorIntentGrants for creatorUid.
- services/core/java/com/android/server/uri/NeededUriGrants.java: removes the public merge(NeededUriGrants other) method (union of uris with a Slog.wtf on mismatched targetPkg/targetUid/flags), since no caller remains. The SecurityException handling (logAndThrowExceptionForIntentRedirect with INTENT_REDIRECT_EXCEPTION_GRANT_URI_PERMISSION) is unchanged.
Completeness
- All three sites in the material that combined caller and creator grants are patched (two in ActivityStarter.java covering both the flagged and non-flagged paths, one in ActivityStartController.java), and the merge helper is removed so the old union pattern cannot be reused.
- The new logic is a gate, not an intersection: when both results are non-null, the caller's intentGrants set is granted as-is; the URIs in creatorIntentGrants are not compared against it. The commit message says the change maintains compatibility with the pre-hardening workflow.
- The fix only applies when IntentCreatorToken.isValid(intent) and the creator UID differs from the calling UID; intents without a valid creator token follow the unchanged single-UID path.
- NeededUriGrants.java keeps now-unused imports (java.util.Objects, android.util.Slog) per the context; cosmetic only.
Behavior changes
- For redirected intents (creator UID != calling UID), if either checkGrantUriPermissionFromIntent call returns null, the target activity receives no URI grants at all; previously it received the non-null party's grants or the union of both.
- URIs that only the creator's check produced are no longer granted; only the caller's computed set is granted, and only when the creator's check also succeeded with a non-null set.
- NeededUriGrants.merge(NeededUriGrants) no longer exists; any out-of-tree caller would fail to compile.
- SecurityException behavior is unchanged: a creator-side failure still goes through logAndThrowExceptionForIntentRedirect; a caller-side failure in ActivityStartController still returns START_CANCELED.
Per major
- Android 14: not affected per the bulletin (not listed). android-security-14.0.0_r29 shows 1/8 lines of the patched region, the same count as on the fixed 16 and 17 tags, so the count does not show whether the affected code exists here.
- Android 15: not affected per the bulletin (not listed). android-security-15.0.0_r17 shows 1/8 lines of the patched region, the same count as on the fixed 16 and 17 tags, so the count does not show whether the affected code exists here.
- Android 16: resolved: same fix, commit 353a1fa497d3 in android-security-16.0.0_r8 (cherry-pick of two internal changes, Change-Id Ica84e8733595bd2dced557bade0811feacbded19), touching NeededUriGrants.java, ActivityStartController.java and ActivityStarter.java.
- Android 17: resolved: same fix, commit 8df772fb59b4 in android-security-17.0.0_r1; identical logic, only surrounding context differs (activityInfo.getUid()/aInfo.getUid() instead of applicationInfo.uid, shifted line numbers).
Unknowns
- The material does not show the semantics of a null return from UriGrantsManagerInternal.checkGrantUriPermissionFromIntent versus a thrown SecurityException, so the exact scenario in which the old OR logic granted unauthorized URIs is not shown.
- The two internal cherry-picked changes referenced in the commit message are not provided separately; only the squashed AOSP diff is available.
- No regression test is included (Test: manual exploit-app verification), so the exact exploit scenario is not shown.
- Whether the Intent Redirection Hardening grant code (creatorIntentGrants computation) exists on Android 14 and 15 is not shown; the bulletin simply does not list those versions.