CVE-2025-48525 AI-generated analysis
platform/frameworks/base, services/companion (CompanionDeviceManagerService / DisassociationProcessor)
When a package's last companion association for a user is removed, the companion device service now revokes notification listener (NLS) access for all of that package's NotificationListenerService components.
Root cause
When an association was removed, nothing revoked notification listener access. On Android 14 the CHANGE_TYPE_REMOVED branch in CompanionDeviceManagerService only called markIdAsPreviouslyUsedForPackage. On 15/16, DisassociationProcessor.disassociate() removed the association and revoked the device-profile role, but not NLS access. The 15/16 diffs delete a comment "// TODO: also revoke notification access" above disassociate(), so the gap was already known. A package could therefore keep NLS access after it no longer had any companion association.
How the fix works
- Android 14, services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java, in the branch for AssociationStore.CHANGE_TYPE_REMOVED (just before markIdAsPreviouslyUsedForPackage): inside Binder.withCleanCallingIdentity, if mAssociationStore.getAssociationsForPackage(userId, packageName).isEmpty(), it calls queryIntentServicesAsUser(new Intent(NotificationListenerService.SERVICE_INTERFACE), 0, userId). For every ResolveInfo whose component package equals packageName, it calls NotificationManager.setNotificationListenerAccessGranted(componentName, false).
- Android 15/16, DisassociationProcessor.java: the same logic is added to disassociate() right after mAssociationStore.removeAssociation(...) and before the role revocation. It uses mAssociationStore.getAssociationsByPackage(userId, packageName), mContext.getPackageManager() and a new injected mNotificationManager field. The constructor gains a NotificationManager parameter.
- Android 15/16, CompanionDeviceManagerService.java constructor: gets NotificationManager via context.getSystemService and passes it to the DisassociationProcessor constructor.
Completeness
- Revocation happens only when the package has zero remaining associations for that userId. While any association remains, NLS access is left alone.
- Every NLS component of the package returned by the query for that user is revoked, whoever granted the access. The code does not track who granted it.
- Android 14 hooks the revocation into the handling of AssociationStore.CHANGE_TYPE_REMOVED (enclosing method not shown). Android 15/16 add it only in DisassociationProcessor.disassociate(), so any removal path that bypasses disassociate() is not covered by the diff shown.
- The grant path is unchanged; the fix only adds cleanup on removal.
Behavior changes
- After a package's last association is removed, setNotificationListenerAccessGranted(component, false) is called for each of the package's NotificationListenerService components returned by the query in that user. This includes access the user may have granted independently.
- The revocation runs with cleared calling identity.
- Android 15/16: the DisassociationProcessor constructor signature changes (extra NotificationManager argument).
Per major
- Android 14: Same fix (single file, in the CHANGE_TYPE_REMOVED branch of CompanionDeviceManagerService). Landed in android-security-14.0.0_r22; 8/8 patched-region lines present at android-security-14.0.0_r29.
- Android 15: Adapted fix: logic moved into DisassociationProcessor.disassociate(int id), with NotificationManager injected from the CompanionDeviceManagerService constructor; uses getAssociationsByPackage. Landed in android-security-15.0.0_r10; 7/8 lines of the patched region present at android-security-15.0.0_r17.
- Android 16: Adapted fix, same as 15 except it uses disassociate(int id, String reason) and removeAssociation(id, reason). Landed in android-security-16.0.0_r3; 7/8 lines present at android-security-16.0.0_r8.
- Android 17: Not affected (not listed by the bulletin); 7/8 lines of the patched region present at android-security-17.0.0_r1.
Unknowns
- How companion apps get NLS access in the first place is not shown in the material.
- The enclosing method of the Android 14 hunk is not shown in the diff header.
- Whether 15/16 have association removal paths that bypass DisassociationProcessor.disassociate().
- Whether querying with flags 0 leaves out any of the package's NLS components (for example disabled ones).
- Which line differs in the 7/8 patched-region comparison on 15/16/17.