CVE-2025-48648 AI-generated analysis
platform/frameworks/base, NotificationManagerService (services/core/java/com/android/server/notification)
NotificationManagerService stops relying on PackageManagerInternal.isSameApp (which, per the commit message, carries an SDK sandbox exception) for its caller-owns-package enforcement and instead compares the caller uid directly against the package uid, and additionally rejects SDK sandbox uids in areNotificationsEnabledForPackage and checks system/SystemUI/shell identity before canNotifyAsPackage in getConversationNotificationChannel.
Root cause
The private same-app enforcement helper in NotificationManagerService.java (the method that throws SecurityException "Package <pkg> is not owned by uid <uid>", after the ROOT_UID/ROOT_PKG early return) delegated to mPackageManagerInternal.isSameApp(pkg, uid, userId). The commit message states this check included an SDK sandbox exception, so a caller whose uid is not the package's uid could still pass the same-package enforcement (likely reached via enforceSystemOrSystemUIOrSamePackage, though the call chain is not shown in the material). areNotificationsEnabledForPackage(pkg, uid) had no explicit rejection of sandbox uids, and getConversationNotificationChannel evaluated canNotifyAsPackage(callingPkg, targetPkg, userId) before the system/SystemUI/shell identity check. The bulletin classifies the result as DoS (High); the material does not show the concrete failure mode.
How the fix works
- NotificationManagerService.java, same-app enforcement helper (hunk around line 10231): replaces `!mPackageManagerInternal.isSameApp(pkg, uid, userId)` with `!UserHandle.isSameApp(uid, mPackageManagerInternal.getPackageUid(pkg, 0L, userId))`. The package's uid in the given user is resolved with PackageManagerInternal.getPackageUid and compared to the supplied uid with UserHandle.isSameApp; no sandbox exception is applied. If the package is not found, getPackageUid returns a negative value and the comparison fails, so the SecurityException is thrown (general platform knowledge).
- NotificationManagerService.java:areNotificationsEnabledForPackage(String pkg, int uid): adds an early `if (Process.isSdkSandboxUid(uid)) return false;` before enforceSystemOrSystemUIOrSamePackage, so a sandbox uid is reported as notifications-disabled without reaching the enforcement or the cross-user permission check.
- NotificationManagerService.java:getConversationNotificationChannel(...): swaps the condition order to `isCallerIsSystemOrSystemUiOrShell() || canNotifyAsPackage(callingPkg, targetPkg, userId)`, so system/SystemUI/shell callers are accepted before canNotifyAsPackage runs on the caller-supplied callingPkg. The helper is also renamed from isCallerIsSystemOrSysemUiOrShell to isCallerIsSystemOrSystemUiOrShell (typo fix).
- Tests: UiServiceTestCase gains mUid/mUserId/mUser/mPkg and per-user UID_N_MR1/UID_O/UID_P/UID_R, and stubs mPmi.getPackageUid for the test packages; NotificationManagerServiceTest drops the blanket `mPackageManagerInternal.isSameApp(...)` answer that made any same-app check pass for mUid, and rewrites tests to use the shared mPmi mock, enqueueNotificationInternal with explicit uids, and shell identity where a system caller is required. PreferencesHelperTest expectations are updated for the new per-user test uids.
Completeness
- The Android 14 commit changes exactly three production sites: the shared same-app enforcement helper, areNotificationsEnabledForPackage, and the condition order in getConversationNotificationChannel. Callers of the patched helper benefit from the change; the material does not show the full set of callers (e.g. whether canNotifyAsPackage and enforceSystemOrSystemUIOrSamePackage route through it).
- The Android 15 and 16 commits additionally convert two direct `mPackageManagerInternal.isSameApp` calls to isCallerSameApp (the config_systemWellbeing AutomaticZenRule type check and getEffectsSuppressor's suppressor-package check) and change resolveNotificationUid so that USER_ALL maps to UserHandle.getUserId(callingUid) instead of USER_SYSTEM. None of these three hunks are present in the Android 14 commit; the material does not show whether the corresponding Android 14 code exists or still calls mPackageManagerInternal.isSameApp directly.
- The sandbox-uid early return is added only to areNotificationsEnabledForPackage; other binder entry points rely on the helper change rather than an explicit Process.isSdkSandboxUid check.
Behavior changes
- areNotificationsEnabledForPackage returns false for any uid for which Process.isSdkSandboxUid is true, before any permission enforcement (no SecurityException for that case).
- The same-app enforcement now throws SecurityException whenever the supplied uid does not match the appId of the package's uid in the given user as reported by PackageManagerInternal.getPackageUid, including for callers that previously passed via the PackageManagerInternal.isSameApp sandbox exception.
- getConversationNotificationChannel no longer runs canNotifyAsPackage for system, SystemUI, shell or root callers; the callingPkg argument is irrelevant for those callers.
- Android 15/16 only: resolveNotificationUid with userId == USER_ALL resolves against the calling uid's user rather than USER_SYSTEM; the wellbeing and effects-suppressor checks go through isCallerSameApp.
Per major
- Android 14: Same fix: commit 699e4677367e in android-security-14.0.0_r25 (cherry-pick of f5605ae25b15, Change-Id I2be1537803a83cf44cc605f2850a3bb705b9bf32); helper change, areNotificationsEnabledForPackage sandbox check, getConversationNotificationChannel reorder, plus large test refactor that introduces mUid/mUserId/UID_* in UiServiceTestCase. 8/8 lines of the patched region present at android-security-14.0.0_r29.
- Android 15: Adapted fix: commit 3d502bcba495 in android-security-15.0.0_r13; same three core hunks, plus conversion of the wellbeing AutomaticZenRule check and getEffectsSuppressor to isCallerSameApp, and resolveNotificationUid USER_ALL now maps to the caller's user. UID_* test constants moved from static values to per-user values. 8/8 lines present at android-security-15.0.0_r17.
- Android 16: Adapted fix: commit b88956f3198d in android-security-16.0.0_r4, with the same production hunks as the Android 15 variant (helper, sandbox check, reorder, wellbeing/getEffectsSuppressor via isCallerSameApp, resolveNotificationUid user mapping); test changes differ slightly. 8/8 lines present at android-security-16.0.0_r8.
- Android 17: Same fix: commit 44ee96106f25 in android-17.0.0_r1 (base); the production hunks match the Android 14 variant (helper, sandbox check, reorder, isCallerIsSystemOrSysemUiOrShell rename), and the commit additionally adds compatibility-device-util-axt-minus-dexmaker to services/tests/uiservicestests/Android.bp and a mUmi UserManagerInternal mock to UiServiceTestCase. 8/8 lines present at android-security-17.0.0_r1.
Unknowns
- The exact name of the patched same-app enforcement helper and its full set of callers (the hunk header does not show it).
- The concrete DoS mechanism and which binder entry point the reporter used; the material only shows the bulletin type and the sandbox-exception explanation from the commit message.
- Whether Android 14's NotificationManagerService has other direct mPackageManagerInternal.isSameApp call sites (e.g. getEffectsSuppressor) that the 15/16 commits changed but the 14 commit did not.
- Whether services/tests/uiservicestests/Android.bp on Android 14 already includes the compatibility util dependency needed by the updated tests.