CVE-2024-49743 AI-generated analysis
platform/frameworks/base, Notification / NotificationManagerService (allowlist token, Parcel class cookies)
Hardens Notification allowlist-token handling: NMS rejects enqueued notifications carrying a token other than its own ALLOWLIST_TOKEN and forces the correct token on records it enqueues, while Notification parceling writes only the root notification's token for all nested notifications and unparceling keeps an existing PendingIntent cookie instead of letting inner notifications replace it.
Root cause
Notification.mAllowlistToken is the token used for background operations for the notification's pending intents (per the Javadoc in the diff). Before the fix: (1) Notification.readFromParcelImpl unconditionally called parcel.setClassCookie(PendingIntent.class, mAllowlistToken), so a nested notification (publicVersion or a Notification inside extras) carrying its own token in a hand-built parcel would replace the cookie, and that token would be passed to PendingIntents unparceled afterwards; (2) Notification.writeToParcelImpl wrote each notification's own mAllowlistToken, so nested notifications were serialized with their own token, independent of the root notification. clearAllowlistToken(), which NMS used before returning notifications to an untrusted app, cleared only the root and publicVersion, not notifications embedded in extras; (3) the NMS enqueue path (after the invalid-package check) did not check the token of an incoming notification; (4) notifications created directly inside NMS (group summaries via EnqueueNotificationRunnable) never went through unparceling and so had no token.
How the fix works
- core/java/android/os/Parcel.java: adds hidden helpers hasClassCookie(Class) and removeClassCookie(Class, Object expectedCookie); removeClassCookie logs Log.wtf if the removed cookie is not the expected one or no cookies were present.
- core/java/android/app/Notification.java:readFromParcelImpl: only sets the PendingIntent.class cookie if the parcel does not already have one, so the first (root) notification's token is used for PendingIntents read afterwards and inner notifications cannot override it.
- core/java/android/app/Notification.java:writeToParcel: if no Notification.class cookie exists on the parcel, this is the root notification. It sets the cookie to its mAllowlistToken (possibly null), calls writeToParcelImpl, then removes the cookie in a finally block. Nested notifications see the cookie present and do not touch it.
- core/java/android/app/Notification.java:writeToParcelImpl: writes parcel.getClassCookie(Notification.class) (the root token) instead of this.mAllowlistToken, so every nested notification is serialized with the root's token (null if the root has none).
- core/java/android/app/Notification.java: clearAllowlistToken() is replaced by overrideAllowlistToken(IBinder), which sets the token on this notification and on publicVersion, and a getAllowlistToken() getter is added; both are @hide.
- services/core/java/com/android/server/notification/NotificationManagerService.java (enqueue path after the invalid-package check): reads notification.getAllowlistToken(); if it is non-null and not identical to ALLOWLIST_TOKEN, throws SecurityException("Unexpected allowlist token received from " + callingUid). It then calls overrideAllowlistToken(ALLOWLIST_TOKEN), so null tokens (for example, notifications posted from inside system_server) are normalized.
- services/core/java/com/android/server/notification/NotificationManagerService.java:EnqueueNotificationRunnable.enqueueNotification: under mNotificationLock, calls r.getNotification().overrideAllowlistToken(ALLOWLIST_TOKEN) so records created directly by NMS (group summaries) get the correct token.
- services/core/java/com/android/server/notification/NotificationManagerService.java (the call site that removes the background token before returning a notification to an untrusted app): now calls notification.overrideAllowlistToken(null). ALLOWLIST_TOKEN is made package-private for tests.
- Tests: ParcelTest covers the new cookie helpers. NotificationManagerServiceTest adds enqueueNotification_acceptsCorrectToken, _acceptsNullToken_andPopulatesIt, _directlyThroughRunnable_populatesAllowlistToken, _rejectsOtherToken, _customParcelingWithFakeInnerToken_hasCorrectTokenInIntents and getActiveNotifications_doesNotLeakAllowlistToken (the last checks publicVersion and WearableExtender pages).
Completeness
- Covers both directions across the binder boundary. Incoming: NMS rejects foreign non-null tokens, and unparceling ignores inner tokens. Outgoing: serialization writes only the root token, so nested notifications in extras, such as WearableExtender pages, no longer carry their own token.
- overrideAllowlistToken(IBinder) itself still recurses only into publicVersion, not into notifications nested in extras. Since writeToParcelImpl now ignores per-object tokens, this does not affect what is parceled.
- The enqueue check accepts a null token and replaces it with ALLOWLIST_TOKEN; it rejects only non-null tokens that differ. Only the enqueue path and EnqueueNotificationRunnable.enqueueNotification are changed; other paths that build NotificationRecords inside NMS are not shown in the diff.
Behavior changes
- enqueueNotificationWithTag (via the NMS enqueue path) now throws SecurityException when the unparceled notification carries a non-null token different from NMS's ALLOWLIST_TOKEN.
- All nested notifications are parceled with the root notification's allowlist token (null if the root has none); before the fix, each carried its own.
- Hidden API change: Notification.clearAllowlistToken() is removed and replaced by overrideAllowlistToken(IBinder) and getAllowlistToken(). Parcel gains hidden hasClassCookie and removeClassCookie; removeClassCookie logs Log.wtf on a mismatch.
- Group summaries and other records created by NMS through EnqueueNotificationRunnable now hold ALLOWLIST_TOKEN instead of null.
Per major
- Android 14: same fix: commit 43240adb7f3e in android-security-14.0.0_r16, unconditional (no flag); patched region present at android-security-14.0.0_r29 (7/8 lines).
- Android 15: adapted fix: commit f1fd60bb80f9 in android-security-15.0.0_r4 adds the EnqueueNotificationRunnable.enqueueNotification override gated by android.app.Flags.secureAllowlistToken(); the test context shows an existing enqueueNotification_rejectsOtherToken test under @EnableFlags(FLAG_SECURE_ALLOWLIST_TOKEN). Patched region present at android-security-15.0.0_r17 (7/8 lines).
- Android 16: not affected per the bulletin (not listed); patched region present at android-security-16.0.0_r8 (7/8 lines).
- Android 17: not affected per the bulletin (not listed); patched region present at android-security-17.0.0_r1 (7/8 lines).
Unknowns
- The material does not show which Android 15 commits introduced the token rejection and the Parcel/Notification serialization changes, nor whether the secureAllowlistToken flag is enabled in the 15 security tags.
- The material does not show the fix commits for Android 12, 12L and 13, which the bulletin lists as affected.
- Which concrete privilege the leaked or injected token confers (handled by PendingIntent allowlist logic) is not shown in the diff.
- Whether readFromParcelImpl leaving the PendingIntent.class cookie set affects parcels that contain several root notifications is not shown.