CVE-2026-0074 AI-generated analysis
platform/frameworks/base (graphics/java/android/graphics/ImageDecoder.java) and platform/packages/apps/Launcher3 (quickstep, QuickstepProcessInitializer / LauncherProcessImageListener)
Adds process-level and thread-level default OnHeaderDecodedListener hooks to android.graphics.ImageDecoder and registers a Launcher (Quickstep) process default listener that rejects non-allowlisted image mime types and downscales images whose estimated decoded size exceeds a configured memory budget (300 MB).
Root cause
Before the fix, ImageDecoder.callHeaderDecoded only invoked the listener passed at decode time, so there was no way to enforce default decode constraints for a whole process or thread. Judging from what the fix adds, images decoded via ImageDecoder in the Launcher process had no cap on decoded size and no mime-type restriction. The new code comment says a "Remote size may have returned a giant image, so we need to defensively limit it". An oversized image could therefore drive excessive memory allocation in the Launcher process. The bulletin rates this as a High severity DoS. The specific Launcher decode call sites are not shown.
How the fix works
- frameworks/base graphics/java/android/graphics/ImageDecoder.java: adds the static fields sDefaultProcessListener (guarded by sListenerLock) and sDefaultThreadListener (ThreadLocal), plus the @hide static setters and getters setDefaultThreadListener, getDefaultThreadListener, setDefaultProcessListener and getDefaultProcessListener.
- frameworks/base ImageDecoder.callHeaderDecoded: now builds the ImageInfo if any of the decode-time listener, the thread default or the process default is non-null. It runs the thread default if set, otherwise the process default, and always runs the decode-time listener last. Defaults can therefore call setTargetSize before the caller's listener runs.
- Launcher3 quickstep/src/com/android/quickstep/LauncherProcessImageListener.kt (new): implements ImageDecoder.OnHeaderDecodedListener. onHeaderDecoded throws IOException when the mime type is null or its lowercase form is not in allowedMimeTypes. Then getPreferredSize computes width*height*8 bytes (BYTES_PER_PIXEL = 8, worst-case RGBA_F16, long arithmetic). If that exceeds maxMemoryBytes, it computes a sqrt-based uniform scale, rounds (x*scale+0.5) with a minimum of 1 pixel, and calls decoder.setTargetSize.
- Launcher3 quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java: init() now calls setupImageDecoder(context). That method reads R.integer.max_launcher_memory_mb and R.array.allowed_image_mime_types, then calls ImageDecoder.setDefaultProcessListener(new LauncherProcessImageListener(maxMemoryBytes, allowedMimeTypes)).
- Launcher3 quickstep/res/values/config.xml: adds max_launcher_memory_mb=300 and an allowlist of image/png, image/jpeg, image/webp, image/gif, image/bmp, image/x-ico, image/vnd.wap.wbmp, image/heif, image/heic and image/avif.
- Tests: LauncherProcessImageListenerTest.kt in Launcher3, plus new CtsGraphicsTestCases testDefaultProcessListener, testDefaultThreadListener and testDefaultsAndLocalListener (per the commit message).
Completeness
- The framework change is only a hook and enforces nothing by itself. Protection exists only in processes that register a default listener. In this fix that is the Quickstep Launcher process, via QuickstepProcessInitializer.
- Only decodes that go through android.graphics.ImageDecoder reach callHeaderDecoded. Image decoding in the Launcher process through other APIs (e.g. BitmapFactory, which does not use OnHeaderDecodedListener (general platform knowledge)) is not covered by this listener.
- If any code sets a thread-level default listener on a thread, the process-level listener is skipped on that thread. A decode-time listener always runs after the defaults and can call setTargetSize again.
- The memory check is an estimate (8 bytes per pixel of the source dimensions). It downscales rather than rejects, so an allowlisted image is still decoded, just smaller. The listener does not consider animation frame count or other per-format costs.
- QuickstepProcessInitializer extends MainProcessInitializer and only the Quickstep subclass is modified, so non-Quickstep Launcher3 builds are not shown to receive a default listener.
- The commit messages cite Flag: com.android.graphics.hwui.flags.image_decoder_default_listener, but neither diff contains a flag check. setDefaultProcessListener is called unconditionally in the shown code.
Behavior changes
- In the Launcher (Quickstep) process, ImageDecoder decodes of images whose mime type is null or not in the allowlist now fail with IOException("Image mime type (...) is not allowed.").
- In the Launcher process, images whose width*height*8 exceeds 300 MB (about 39.3 megapixels) are decoded at a reduced target size that preserves aspect ratio.
- ImageDecoder.callHeaderDecoded now builds an ImageInfo and invokes default listeners even when the caller passed no listener, if a thread or process default is set.
- New @hide static APIs on ImageDecoder: setDefaultThreadListener, getDefaultThreadListener, setDefaultProcessListener and getDefaultProcessListener. The process default is set and read under sListenerLock.
Per major
- Android 14: same fix: frameworks/base bf47585e8618 (ImageDecoder default listeners) and Launcher3 c466fd8d9215 (LauncherProcessImageListener + QuickstepProcessInitializer.setupImageDecoder) in android-security-14.0.0_r28. 8/8 lines of the patched region are present at android-security-14.0.0_r29.
- Android 15: adapted fix: Launcher3 8fd9af41d56b in android-security-15.0.0_r16. Same logic with context differences (config.xml taskbar_icon_size 44; QuickstepProcessInitializer has a BinderTracker block before setupImageDecoder). 8/8 lines are present at android-security-15.0.0_r17. The frameworks/base counterpart for 15 is not shown in the material.
- Android 16: same fix: frameworks/base 4abc0b8da1e9 (ImageDecoder default listeners, identical diff at a different line offset) in android-security-16.0.0_r7. 7/7 lines are present at android-security-16.0.0_r8. The Launcher3 counterpart for 16 is not shown in the material.
- Android 17: adapted fix: Launcher3 56e4dc5d2b9a in android-17.0.0_r1 (base). Same logic with QuickstepProcessInitializer context differences. 7/8 lines of the patched region are present at android-security-17.0.0_r1 (the differing line is not shown). The frameworks/base counterpart for 17 is not shown in the material.
Unknowns
- Which Launcher code path decodes the attacker-influenced image (the specific ImageDecoder call site and image source).
- Whether the aconfig flag image_decoder_default_listener gates anything at runtime outside the shown diffs.
- Whether the frameworks/base commit landed on 15 and 17 and the Launcher3 commit on 16. This is not shown in the material, although the resolver marks all majors resolved.
- What the single differing line in the Android 17 patched region (7/8) is.