CVE-2025-48535 AI-generated analysis
platform/packages/apps/Settings, users/AppRestrictionsFragment (app restrictions UI for restricted users)
In Settings' AppRestrictionsFragment, the custom restrictions Intent supplied by an app is no longer launched as received after vetting. Settings now launches a copy pinned to the exact component and package that passed the resolveActivity/package check, and a second commit makes that same copy the object that is resolved.
Root cause
RestrictionsResultReceiver.onReceive takes an Intent from the broadcast result extras (Intent.EXTRA_RESTRICTIONS_INTENT, supplied by the target app). If the fragment is resumed and invokeIfCustom is set, it calls assertSafeToStartCustomActivity(restrictionsIntent). That method resolved the intent with mPackageManager.resolveActivity(intent, MATCH_DEFAULT_ONLY) and checked that the resolved activityInfo.packageName equals the expected packageName, but it returned nothing. The caller then called startActivityForResult(new Intent(restrictionsIntent), requestCode) on a fresh copy of the untrusted intent, and never set the vetted component or package on that copy. The check and the launch therefore ran on different Intent objects, and the launch did not carry the verified component. As a result, the activity that Settings (a highly privileged app) actually started was not guaranteed to be the one that passed the package check.
How the fix works
- Commit 33a1cbf7bd0e, src/com/android/settings/users/AppRestrictionsFragment.java: RestrictionsResultReceiver.assertSafeToStartCustomActivity now returns an Intent. Once the existing resolveActivity and packageName equality checks pass, it creates new Intent(intent), calls setComponent(activityInfo.getComponentName()) and setPackage(activityInfo.packageName) using the ActivityInfo that was vetted, and returns that Intent.
- Commit 33a1cbf7bd0e, RestrictionsResultReceiver.onReceive: stores the return value in a final Intent vettedIntent and passes vettedIntent to AppRestrictionsFragment.this.startActivityForResult instead of new Intent(restrictionsIntent). The ActivityNotFoundException/SecurityException catch path (log, EventLog 200688991, return) is unchanged.
- Commit ca8f012da894 (applied after the first), assertSafeToStartCustomActivity: moves final Intent vettedIntent = new Intent(intent) to the top of the method, before resolveActivity, and resolves vettedIntent instead of the incoming intent. The same copy is now resolved, checked, pinned and launched. The later declaration of vettedIntent is removed.
Completeness
- Covers the single launch site in RestrictionsResultReceiver.onReceive. The launched Intent now has an explicit component and package equal to what resolveActivity returned and what the package check validated.
- Neither commit strips URI permission grant flags, data or ClipData from the untrusted Intent. At android-security-14.0.0_r29 the method also calls removeFlags(FLAG_GRANT_READ/WRITE/PERSISTABLE/PREFIX_URI_PERMISSION), setData(null) and setClipData(null), but that hardening comes from a change not included in this material.
- The package check itself (packageName.equals(activityInfo.packageName) with MATCH_DEFAULT_ONLY) is unchanged. In these two commits, other extras of the app-provided Intent are still forwarded as-is.
- Per index search, no other caller of assertSafeToStartCustomActivity was found. The method is private to the receiver.
Behavior changes
- The custom restrictions activity is now started with an explicit ComponentName and package on the Intent, so the launched activity is always the one resolveActivity returned.
- No new error paths: on failure, Settings still logs 'Cannot start restrictionsIntent', writes EventLog tag 0x534e4554/200688991 and returns without launching.
- assertSafeToStartCustomActivity now returns Intent instead of void (internal, private).
Per major
- Android 14: same fix: two cherry-picks in android-security-14.0.0_r22, first 33a1cbf7bd0e (use vetted component), then ca8f012da894 (copy before resolve). 8/8 patched lines present at android-security-14.0.0_r29, plus later extra hardening (removeFlags/setData/setClipData) not in this material.
- Android 15: same fix: 1e4423730f87 'startActivityForResult with earlier new Intent' in android-security-15.0.0_r10, same diff as ca8f012da894. Its context shows the vetted-component change was already present, but the material does not show the commit that added it. 8/8 lines present at android-security-15.0.0_r17.
- Android 16: same fix: 25ed933c4cf5 in android-security-16.0.0_r1, same diff as 15. 8/8 lines present at android-security-16.0.0_r8.
- Android 17: not affected (not listed by the bulletin). Patched region present (8/8 lines) at android-security-17.0.0_r1.
Unknowns
- The exact mechanism by which the unpinned launch reached a different target than the vetted one. The commits only say 'See bug' for bugs 353680402 and 365739560.
- Which commit added the URI-grant flag, data and ClipData stripping visible at android-security-14.0.0_r29, and whether it is tied to this CVE or another one.
- Which commit brought the vetted-component change to Android 15 and 16.
- Whether Android 13 (listed as affected) received the same commits. The material includes no Android 13 commit.
- Which user actions cause RestrictionsResultReceiver to be created with invokeIfCustom true.