Enforce a hard limit for the size of images to be decoded

Although standards like JPEG allow for sizes as large as
65,535×65,535 pixels, we realistically cannot fit such large images in
memory.

Created a test gif since it's the smallest format I could find. It's
larger than strictly necessary for the test in case we want to increase
the hard limit in the future.

Bug: 444671303
Test: manually verified that a notification that causes a crashloop
without this change, doesn't anymore
Test: LocalImageResolverTest
Flag: EXEMPT CVE_FIX

(cherry picked from commit 449f35b532d5f680b90c8f9d8150010e7f5f30df)
Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:4bceac8d45f07c272eced0b8da51513415d7d248
Merged-In: I0c95cfeabe169630286e7af8577c4495c2b1015f
Change-Id: I0c95cfeabe169630286e7af8577c4495c2b1015f
diff --git a/core/java/com/android/internal/widget/LocalImageResolver.java b/core/java/com/android/internal/widget/LocalImageResolver.java
index c5d74fc..6351c0e 100644
--- a/core/java/com/android/internal/widget/LocalImageResolver.java
+++ b/core/java/com/android/internal/widget/LocalImageResolver.java
@@ -47,6 +47,12 @@
     static final int DEFAULT_MAX_SAFE_ICON_SIZE_PX = 480;
 
     /**
+     * If an image is larger than this, we won't even attempt to decode it, as we risk taking up all
+     * of the device memory.
+     */
+    private static final int DEFAULT_DECODE_HARD_LIMIT_PX = 4096;
+
+    /**
      * Resolve an image from the given Uri using {@link ImageDecoder} if it contains a
      * bitmap reference.
      * Negative or zero dimensions will result in icon loaded in its original size.
@@ -252,6 +258,16 @@
     private static void onHeaderDecoded(ImageDecoder decoder, ImageDecoder.ImageInfo info,
             int maxWidth, int maxHeight) {
         final Size size = info.getSize();
+
+        if (size.getWidth() > DEFAULT_DECODE_HARD_LIMIT_PX
+                || size.getHeight() > DEFAULT_DECODE_HARD_LIMIT_PX) {
+            // The image is larger than what we can reasonably expect to decode without filling up
+            // the device memory, so let's bail.
+            throw new RuntimeException(
+                    "Image dimensions (" + size.getWidth() + "x" + size.getHeight()
+                            + ") exceed the maximum allowed size.");
+        }
+
         final int originalSize = Math.max(size.getHeight(), size.getWidth());
         final int maxSize = Math.max(maxWidth, maxHeight);
         final double ratio = (originalSize > maxSize)
diff --git a/core/tests/coretests/res/drawable/test16000x16000.gif b/core/tests/coretests/res/drawable/test16000x16000.gif
new file mode 100644
index 0000000..d62dded
--- /dev/null
+++ b/core/tests/coretests/res/drawable/test16000x16000.gif
Binary files differ
diff --git a/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java b/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java
index 271a20b..e2ce346 100644
--- a/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java
@@ -282,6 +282,14 @@
         assertThat(d).isNull();
     }
 
+    @Test(expected = IOException.class)
+    public void resolveImage_veryLargeResource_throwsException() throws IOException {
+        // Passing in an unreasonably large image should throw an exception.
+        Uri uri = Uri.parse("android.resource://"
+                + mContext.getPackageName() + "/" + R.drawable.test16000x16000);
+        LocalImageResolver.resolveImage(uri, mContext);
+    }
+
     @Test
     public void resolveResourcesForIcon_notAResourceIcon_returnsNull() {
         Icon icon = Icon.createWithContentUri(Uri.parse("some_uri"));