CVE-2024-31325 AI-generated analysis
platform/frameworks/base, SystemUI notification row inflation (ExpandableNotificationRow / NotificationInlineImageResolver / RowInflaterTask)
SystemUI now constructs each ExpandableNotificationRow's NotificationInlineImageResolver with a Context bound to the notification's own user (via createContextAsUser when it differs from SystemUI's user) instead of SystemUI's application context, so inline message/conversation image Uris are resolved with the notification's user id.
Root cause
ExpandableNotificationRow(Context, AttributeSet) passed its context to NotificationInlineImageResolver. The resolver constructor replaced it with context.getApplicationContext(), SystemUI's own application context, and stored that in mContext for image Uri resolution. Message/conversation image Uris carried in a notification were therefore resolved with SystemUI's user id, whichever user posted the notification. RowInflaterTask inflated the row from R.layout.status_bar_notification_row through a plain AsyncLayoutInflater, which had no way to pass the NotificationEntry (and thus the user) into the row constructor.
How the fix works
- ExpandableNotificationRow.java: adds a new constructor ExpandableNotificationRow(Context, AttributeSet, NotificationEntry) that delegates to a private constructor (Context sysUiContext, AttributeSet, Context userContext). The view itself is still built with the SystemUI context, but mImageResolver is created with userContext.
- ExpandableNotificationRow.java:userContextForEntry: returns the base context when base.getUserId() equals entry.getSbn().getNormalizedUserId(); otherwise returns base.createContextAsUser(UserHandle.of(normalizedUserId), 0). This binds Uri resolution to the notification's user.
- ExpandableNotificationRow.java: the two-argument constructor is kept but now logs Log.wtf(TAG, "This constructor shouldn't be called") and uses the passed context as the user context. mImageResolver becomes final.
- NotificationInlineImageResolver.java:constructor: stores the passed context directly (mContext = context) instead of context.getApplicationContext(), so the user-bound context is not discarded. It also adds a @VisibleForTesting getContext() accessor.
- RowInflaterTask.java: constructs AsyncLayoutInflater with a new RowAsyncLayoutInflater (implements androidx AsyncLayoutFactory) that holds the NotificationEntry. Its onCreateView intercepts the tag named ExpandableNotificationRow.class.getName() and creates the row with the new three-argument constructor. All other tags return null.
- Tests: ExpandableNotificationRowTest adds imageResolver_sameNotificationUser_usesContext and imageResolver_differentNotificationUser_createsUserContext. NotificationTestHelper.generateRow now builds the NotificationEntry first and installs RowInflaterTask.RowAsyncLayoutInflater as Factory2 before inflating. SysuiTestableContext gains prepareCreateContextAsUser and a createContextAsUser override.
Completeness
- Covers rows inflated through RowInflaterTask, the inflation path changed in the diff. The resolver's context is now scoped to the notification's user. NotificationInlineImageResolver uses it for Uri loads, and per index search NotificationInlineImageCache preload tasks use the resolver set through setImageResolver.
- The two-argument ExpandableNotificationRow(Context, AttributeSet) constructor remains and falls back to the caller's context, logging Log.wtf. Any other code that inflates status_bar_notification_row without RowAsyncLayoutInflater would still get a resolver bound to SystemUI's user. The material shows no such production caller.
- User selection relies on StatusBarNotification.getNormalizedUserId(). How USER_ALL notifications are handled depends on that normalization, which this patch does not change.
- Only the inline image resolver path is changed. The material does not show whether other code that loads notification-supplied Uris needed changes.
Behavior changes
- For notifications whose normalized user differs from SystemUI's user, a per-row Context is created with createContextAsUser and used for image Uri resolution.
- NotificationInlineImageResolver no longer unwraps to the application context; it keeps whatever Context it is given.
- Inflating ExpandableNotificationRow through the two-argument constructor now emits a Log.wtf.
- RowInflaterTask now always installs a custom AsyncLayoutFactory on its AsyncLayoutInflater.
Per major
- Android 14: same fix: commit d147a3319793 in android-security-14.0.0_r9 (cherry-pick of 44524f06f9d6); at android-security-14.0.0_r29, 8/8 lines of the patched region are present.
- Android 15: not affected per the bulletin (not listed); 8/8 lines of the patched region are present at android-security-15.0.0_r17, consistent with the fix being present.
- Android 16: not affected per the bulletin (not listed); 7/8 lines of the patched region are present at android-security-16.0.0_r8; which line differs is not shown.
- Android 17: not affected per the bulletin (not listed); 7/8 lines of the patched region are present at android-security-17.0.0_r1; which line differs is not shown.
Unknowns
- The fix status for Android 12, 12L and 13, which the bulletin lists as affected, is not shown in the per-major facts.
- Whether 12/12L/13 branches have the androidx AsyncLayoutFactory API that the fix needs is not shown.
- Which content providers or user data could be reached through the misresolved Uris is not shown in the material.
- Whether other SystemUI code paths that load notification-supplied Uris had the same user-id issue is not shown.
- Which line of the patched region differs on Android 16 and 17 is not shown.