CVE-2025-48607 AI-generated analysis
platform/frameworks/base, AppOps (AppOpsService / AppOpsManager / IAppOpsService.aidl)
Changes the IAppOpsService.getPackagesForOpsForDevice binder method to return a ParceledListSlice<AppOpsManager.PackageOps> instead of a plain List, so that large result sets are paginated across binder transactions instead of overflowing a single one.
Root cause
AppOpsService.getPackagesForOpsForDevice(int[] ops, String persistentDeviceId) built a List<AppOpsManager.PackageOps> and returned it in a single binder reply. Per the commit message, an earlier fix (ag/31434165) capped the number of attributed op entries per package, but the number of packages in the reply was still unbounded. A PoC app created attributed op entries for hundreds of packages, which caused TransactionTooLargeException in the getPackagesForOpsForDevice binder API. The commit message says SystemUI crashed, and implies the device failed to boot, since it says the device could boot after the fix.
How the fix works
- core/java/com/android/internal/app/IAppOpsService.aidl: the declaration of getPackagesForOpsForDevice(in int[] ops, String persistentDeviceId) now returns ParceledListSlice<AppOpsManager.PackageOps> instead of List<AppOpsManager.PackageOps>. The method keeps its position (last method in the interface on Android 15, before noteOperationsInBatch on Android 16), so transaction codes do not change.
- services/core/java/com/android/server/appop/AppOpsService.java:getPackagesForOpsForDevice: the return type becomes ParceledListSlice<AppOpsManager.PackageOps>, and the final `return res;` becomes `return res == null ? null : new ParceledListSlice<>(res);`. The permission check (mContext.checkPermission for hasAllPackageAccess) and the loop that builds the result are untouched.
- services/core/java/com/android/server/appop/AppOpsService.java:getPackagesForOps(int[] ops): still returns List<AppOpsManager.PackageOps>. It now calls getPackagesForOpsForDevice(ops, PERSISTENT_DEVICE_ID_DEFAULT) and unwraps the result with packageOps.getList(), keeping null as null.
- core/java/android/app/AppOpsManager.java:getPackagesForOps(String[] ops, String persistentDeviceId): receives the ParceledListSlice from mService.getPackagesForOpsForDevice and returns packageOps.getList(), or Collections.emptyList() when the result is null. The public return type is unchanged.
- core/java/android/app/AppOpsManager.java:getPackagesForOps(int[] ops) (@UnsupportedAppUsage): unwraps the same way with getList() and returns null when the service returned null, so the earlier null behavior is kept.
- Imports: AppOpsService.java gains android.content.pm.ParceledListSlice (needed) and java.io.StringWriter (no hunk in the diff uses it; it appears incidental). IAppOpsService.aidl already imported android.content.pm.ParceledListSlice.
Completeness
- Covers the single binder method getPackagesForOpsForDevice and both AppOpsManager client wrappers that call it (getPackagesForOps(String[], String) and getPackagesForOps(int[])). Null handling is kept on both sides.
- The fix changes only the transport. The total number of PackageOps returned is still unbounded: ParceledListSlice splits the list across transactions, but each PackageOps element must still fit on its own. That relies on the earlier per-package attributed-op-entry threshold mentioned in the commit message, which is not part of this diff.
- AppOpsService.getPackagesForOps(int[] ops) keeps its @Override and its List return type. If that method is itself declared in IAppOpsService.aidl (its declaration is not in the AIDL excerpt shown), a direct binder caller would still receive an unpaginated List. The in-tree AppOpsManager wrappers no longer use it.
- This commit does not touch other AppOps binder methods that return op state lists. The material does not show whether their replies can grow in the same way.
Behavior changes
- The reply of the getPackagesForOpsForDevice binder transaction changes on the wire from a List<PackageOps> to a ParceledListSlice. Any out-of-tree code that calls the hidden IAppOpsService interface directly must be rebuilt against the new AIDL.
- Large results are delivered over several binder round trips instead of one, because ParceledListSlice fetches the remaining elements from the server on demand (general platform knowledge). Callers see the same List contents.
- Permission checks, filtering, and the null/empty-list return behavior of the public AppOpsManager APIs do not change.
Per major
- Android 14: Not affected (not listed by the bulletin). The material shows no Android 14 code for this method. The 5/8 lines region metric is the same on every major, so it tells nothing either way.
- Android 15: Same fix: platform/frameworks/base 2114590c98f3 in android-security-15.0.0_r12 (cherry-pick, Change-Id If4590160d1d743f58c1aed3f923d3668a8936c57), touching AppOpsManager.java, IAppOpsService.aidl and AppOpsService.java.
- Android 16: Same fix: platform/frameworks/base 03d704069914 in android-security-16.0.0_r3. The logic is identical. The AIDL hunk also re-indents the adjacent noteOperationsInBatch line (whitespace only), and line offsets differ.
- Android 17: Not affected (not listed by the bulletin). The material does not show the code for 17.
Unknowns
- Whether getPackagesForOps(int[]) is itself declared in IAppOpsService.aidl (the AIDL excerpt does not include it), and so whether a direct binder path still returns an unpaginated List.
- Whether Android 14 and 17 contain the affected method. The bulletin does not list them, and the material shows no code from those branches.
- Details of the earlier fix ag/31434165 (the per-package attributed-op-entry threshold) that this change builds on.