CVE-2026-0034 AI-generated analysis
platform/frameworks/base, services/core notification (ManagedServices, NotificationManagerService: listeners, assistants, condition providers)
Two frameworks/base commits bound the per-user ManagedServices approval state: uninstalling a package now also removes its entries from mUserSetServices, and setPackageOrComponentEnabled refuses to add more than MAX_SERVICE_ENTRIES (100) entries per user to mApproved / mUserSetServices, returning a boolean so NotificationManagerService callers skip follow-up work when nothing changed.
Root cause
In services/core/java/com/android/server/notification/ManagedServices.java, setPackageOrComponentEnabled unconditionally added pkgOrComponent to the per-user approved ArraySet (mApproved) and to mUserSetServices, with no upper bound on how many packages/components could be approved (and therefore bound) per user. In addition, the uninstall cleanup path reached from onPackagesChanged only trimmed the approved lists; entries for the removed package stayed in mUserSetServices, so that list could only grow. Every call also triggered rebindServices and the callers' broadcasts regardless of whether anything changed.
How the fix works
- Commit 1 (00fd75533614, Ibd585dd1af): ManagedServices.java, in the uninstall cleanup method invoked from onPackagesChanged (the method taking uninstalledUserId and pkg that returns `removed`, around line 1265): after trimming the approved lists, it fetches mUserSetServices.get(uninstalledUserId) and iterates it backwards, removing every entry whose getPackageName(pkgOrComponent) equals the uninstalled pkg. New test testPackageUninstall_componentNoLongerUserSetList verifies isPackageOrComponentUserSet becomes false after onPackagesChanged(true, pkg, uid) for both APPROVAL_BY_COMPONENT and APPROVAL_BY_PACKAGE.
- Commit 2 (cb1ce8020b43, Iddd8044997): ManagedServices.java adds `private static final int MAX_SERVICE_ENTRIES = 100`. Both overloads of setPackageOrComponentEnabled now return boolean. In the 5-arg version: when enabled, approved.add(approvedItem) happens only if approved.size() < MAX_SERVICE_ENTRIES (sets changed=true), otherwise a Slog.w 'Failed to allow ... because there are too many already' and no change; disabling always removes and sets changed=true. The mUserSetServices update is now inside `if (changed)`: with userSet=true the entry is added only if userSetServices.size() < MAX_SERVICE_ENTRIES, with userSet=false it is removed. rebindServices(false, userId) runs only if changed; the method returns changed.
- Commit 2, NotificationManagerService.java setNotificationPolicyAccessGrantedForUser: captures the boolean from mConditionProviders.setPackageOrComponentEnabled(pkg, userId, true, granted) and returns early (no ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED broadcast, no handleSavePolicyFile) when false.
- Commit 2, NotificationManagerService.java setNotificationListenerAccessGrantedForUser internals: reorders the calls so mListeners.setPackageOrComponentEnabled(listener.flattenToString(), userId, true, granted, userSet) runs first; if it returns false the method returns before calling mConditionProviders.setPackageOrComponentEnabled(..., false, granted, userSet) and before the broadcast.
- Commit 2, the assistant-services setPackageOrComponentEnabled override in NotificationManagerService.java (the one ensuring only one component is enabled, calling setNotificationAssistantAccessGrantedForUserInternal): now returns boolean; the 'already the current component' early exit returns false; otherwise returns super's result. The listener-services override (which sends ACTION_NOTIFICATION_LISTENER_ENABLED_CHANGED): returns false without sending the broadcast when super returned false, else sends it and returns true.
- Tests: ManagedServicesTest adds setPackageOrComponentEnabled_tooManyPackages_stopsAdding (101st add rejected, disabling frees a slot) and setPackageOrComponentEnabled_tooManyChanges_stopsAddingToUserSet (user-set list capped; uninstall via onPackagesChanged frees space). NotificationManagerServiceTest stubs all mock setPackageOrComponentEnabled variants to return true and adds testSetListenerAccessForUser_tooManyListeners_skipsFollowups.
Completeness
- The cap is enforced only in ManagedServices.setPackageOrComponentEnabled; the material does not show whether other writers of mApproved / mUserSetServices (XML read/migration paths such as migrateToXml used by the tests) apply any bound.
- The user-set cap is independent of the approved cap: when mUserSetServices for a user already has 100 entries, the approval itself still succeeds (changed=true) but the entry is not recorded as user-set (shown by the 'toomany' assertions in setPackageOrComponentEnabled_tooManyChanges_stopsAddingToUserSet).
- Disabling always sets changed=true (approved.remove is not checked for actual presence), so a disable for an entry that was never approved still triggers rebindServices and the callers' broadcasts.
- Cleanup of mUserSetServices on uninstall covers only the uninstalled user id passed to the cleanup method; it keys on package name via getPackageName, so it works for both package and component entries.
- The commit message lists a flag (com.android.server.notification.limit_managed_services_count) but the Android 14 diff contains no flag check; the limit is unconditional in this backport.
- The 14/15/16 per-major facts report 7/8 lines of the patched region present at the latest security tags, so a small drift versus the original hunk exists there; the material does not show which line differs.
Behavior changes
- Attempting to approve a 101st package/component per user for a given ManagedServices instance is silently rejected (Slog.w only, no exception, no rebind, no broadcast, no policy file save on the condition-provider path).
- setPackageOrComponentEnabled (both overloads, and the assistant/listener overrides in NotificationManagerService.java) now return boolean instead of void.
- rebindServices is no longer called when the enable request was rejected; ACTION_NOTIFICATION_LISTENER_ENABLED_CHANGED and ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED are not broadcast in that case.
- In setNotificationListenerAccessGrantedForUser the listener list is updated before the condition-provider secondary list, and the condition-provider update is skipped when the listener update was rejected.
- The assistant override returns false (previously returned void) when the requested component is already the enabled assistant.
- Uninstalling a package now removes its entries from mUserSetServices, so isPackageOrComponentUserSet returns false afterwards.
- At most 100 entries per user are kept in mUserSetServices; additional user-set markers are dropped.
Per major
- Android 14: same fix: both commits (00fd75533614 then cb1ce8020b43) landed in android-security-14.0.0_r25; the listener override in 14 has no redactSensitiveNotificationsFromUntrustedListeners block, so the hunk is the simpler 14 variant. 7/8 patched lines present at r29.
- Android 15: adapted fix: a68f6e5719ce in android-security-15.0.0_r13 is the same logic; the listener setPackageOrComponentEnabled hunk inserts the early return before the redactSensitiveNotificationsFromUntrustedListeners block, and line offsets differ. 7/8 patched lines present at r17.
- Android 16: adapted fix: e363f8210456 in android-security-16.0.0_r4, same content as 15 with different line offsets. 7/8 patched lines present at r8.
- Android 17: not affected per the bulletin (not listed); at android-security-17.0.0_r1 the patched region is present (2/2 lines).
Unknowns
- The exact name of the ManagedServices cleanup method modified by commit 1 (the hunk header shows only the class; the method takes uninstalledUserId/pkg and returns `removed`).
- Whether mApproved / mUserSetServices are persisted and how; the material shows only XML attribute constants.
- Whether XML load/migration paths or other writers of mApproved can exceed MAX_SERVICE_ENTRIES; not shown in the material.
- Which single line of the patched region differs at the latest 14/15/16 security tags (7/8 lines present).
- The concrete privilege impact the bulletin's EoP rating refers to; the material only shows the unbounded list growth and missing cleanup.