ganado / google-security-research

Automatically exported from code.google.com/p/google-security-research
0 stars 0 forks source link

Android BitmapFactory.decodeStream JPG allocPixelRef integer overflow #252

Closed GoogleCodeExporter closed 9 years ago

GoogleCodeExporter commented 9 years ago
The BitmapFactory class is commonly used by Android applications to
display images. It is idiomatic to process an untrusted binary blob
with the decodeStream method to convert an arbitrary image type (JPG,
PNG, GIF, etc) to a bitmap, and then use an ImageView to display the
resulting bitmap.

The output bitmap is allocated using the dimensions of the decoded
image. There are a number of potential allocator implementations for
different cases, such as when a Bitmap object is reused, or if scaling
is required. However, the default allocator is the JavaPixelAllocator
(frameworks/base/core/jni/android/graphics/Graphics.cpp).

Each allocator implements an "allocPixelRef" member function that
takes an SkBitmap object as an argument. The SkBitmap is SKIA's
internal representation of the output bitmap. At the time of
allocPixelRef being called, the SkBitmap holds the decoded image
dimensions, but not the image contents.

The following code snippet is from
JavaPixelAllocator::allocateJavaPixelRef (which is called from
JavaPixelAllocator::alloxPixelRef):

jbyteArray GraphicsJNI::allocateJavaPixelRef(JNIEnv* env, SkBitmap* bitmap,
                                            SkColorTable* ctable) {
...
   const size_t size = bitmap->getSize();

   jbyteArray arrayObj = (jbyteArray)
env->CallObjectMethod(gVMRuntime, gVMRuntime_newNonMovableArray,
gByte_class, size);
...
   jbyte* addr = (jbyte*) env->CallLongMethod(gVMRuntime,
gVMRuntime_addressOf, arrayObj);
...
   SkPixelRef* pr = new AndroidPixelRef(env, info, (void*) addr,
           bitmap->rowBytes(), arrayObj, ctable);
   bitmap->setPixelRef(pr)->unref();
...
   return arrayObj;
}

The allocation routine uses JNI to allocate a new byte array that will
be used as backing memory for the output bitmap's pixels. The size of
the array is calculated using SkBitmap::getSize, as follows:

   size_t getSize() const { return fInfo.fHeight * fRowBytes; }

The fInfo.fHeight value is the height dimension of the decoded image.
The fRowBytes value is the width dimension multiplied by a
"bytes-per-pixel" constant. The "bytes-per-pixel" constant is
established based on the decoded image's color type - valid constant
values include 1, 2, and 4.

On 32-bit systems it is possible to cause an integer overflow in
SkBitmap::getSize by using a specially crafted JPEG file. The default
color type for JPEG images results in a "bytes-per-pixel" constant
value of 4, so any image with decoded width * height dimensions of >=
0x40000000 will overflow getSize.

This results in an undersized allocation for the output bitmap's pixel
data, and a subsequent heap overflow in SKIA's JPEG decoder routine
(specifically the jpeg_read_scanlines loop).

Note that the JavaPixelAllocator::allocPixelRef function is used by
SKIA for all supported image types (JPG, PNG, GIF, etc). It was found
that JPEG is the only suitable decoder for triggering this issue.
Other decoders either have explicit checks for large image dimensions
(WEBP, PNG, BMP), or use a 1-byte-per-pixel color type (GIF, WBMP), or
have tightly bounded image dimensions by specification (ICO).

A 33284x32260 JPEG image that triggers heap corruption on AOSP master
and a Nexus 7 device running 5.0.2 is attached (pixie02.jpg). It was
created by exporting a 33284x32260 canvas as a TarGA in GIMP and then
using a 64-bit build of cjpeg with the "-optimize" flag. This is
necessary to create a large dimension image with a low memory
footprint - simply creating the JPEG image directly in GIMP is not
sufficient, as this leads to memory exhaustion in the Android image
decoder.

This issue could be resolved either by checking for overflow in
JavaPixelAllocator::allocPixelRef, or by adding a bounds check in
SKIA's SkJPEGImageDecoder::onDecode method. A change to
JavaPixelAllocator::allocPixelRef could adopt the same
getSafeSize64/sk_64_isS32 primitive that is used in the
RecyclingPixelAllocator (sample patch attached).

This bug is subject to a 90 day disclosure deadline. If 90 days elapse
without a broadly available patch, then the bug report will automatically
become visible to the public.

Original issue reported on code.google.com by haw...@google.com on 4 Feb 2015 at 11:39

Attachments:

GoogleCodeExporter commented 9 years ago

Original comment by haw...@google.com on 20 Feb 2015 at 10:30

GoogleCodeExporter commented 9 years ago
This issue has been resolved in Android 5.1. The following AOSP patch shows the 
fix:

https://android.googlesource.com/platform/frameworks/base.git/+/89a2466a2a2859df
5f29350117efd7f9d6a2e32f

We've been informed that this bug only affected Android 5.0 (KitKat and earlier 
were not affected). 

Original comment by haw...@google.com on 12 Mar 2015 at 6:25

GoogleCodeExporter commented 9 years ago

Original comment by cev...@google.com on 12 Mar 2015 at 7:36