ericmckean / google-security-research

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

Android BitmapFactory.decodeStream 9patch PNG heap overflow #234

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.

9-patch (also known as NinePatch) is an Android-specific extension to
the PNG image format that allows for automatic scaling of images.

The BitmapFactory.decodeStream method is implemented in
frameworks/base/graphics/java/android/graphics/BitmapFactory.java, but
uses JNI to invoke the nativeDecodeStream function in
frameworks/base/core/jni/android/graphics/BitmapFactory.cpp.

The doDecode method calls into SKIA to fetch an appropriate decoder
class for the given input stream. It uses the decoder's "setPeeker"
member function to install a NinePatchPeeker. The peeker is only used
with PNG files, specifically SKIA installs it as a
png_set_read_user_chunk_fn callback.

The PNG library (libpng) uses png_set_read_user_chunk_fn to offer
extensibility - chunk types that are unrecognized by libpng can be
handled by user-supplied functions. The NinePatchPeeker implements
code for "npTc" chunks, with the following data structure:

    int8_t wasDeserialized;
    int8_t numXDivs;
    int8_t numYDivs;
    int8_t numColors;
    uint32_t xDivsOffset;
    uint32_t yDivsOffset;
    int32_t paddingLeft, paddingRight;
    int32_t paddingTop, paddingBottom;
    uint32_t colorsOffset;
    uint32_t data[0]; /* should contain numXDivs + numYDivs + numColors items */

The following code (from
frameworks/base/core/jni/android/graphics/NinePatchPeeker.cpp) is
executed when a PNG with a "npTc" chunk is supplied:

    if (!strcmp("npTc", tag) && length >= sizeof(Res_png_9patch)) {
        Res_png_9patch* patch = (Res_png_9patch*) data;
        size_t patchSize = patch->serializedSize();
        assert(length == patchSize);
        Res_png_9patch* patchNew = (Res_png_9patch*) malloc(patchSize);
        unsigned char *p = (unsigned char *) patchNew;
        memcpy(patchNew, patch, patchSize);
        Res_png_9patch::deserialize(patchNew);
        patchNew->fileToDevice();
...

The size of the 9-patch structure is calculated using serializedSize
(in frameworks/base/libs/androidfw/ResourceTypes.cpp) as follows:

    size_t Res_png_9patch::serializedSize() const
    {
        return 32
                + numXDivs * sizeof(int32_t)
                + numYDivs * sizeof(int32_t)
                + numColors * sizeof(uint32_t);
    }

Note that the arithmetic is performed as int8_t and will be
sign-extended prior to being cast to a size_t. This means that
supplying a negative int8_t (such as 0xFF) could result in a return
value that is equal to or smaller than 32 (the size of the 9-patch
structure header). For instance, consider setting numXDivs == 0x7F,
numYDivs == 0x81, numColors == 0x00. In this case the return value
will be 32 (0x20) despite have a positive non-zero numXDivs.

The next lines of the "npTc" processing are an assert (not relevant to
release builds, but also bypassable in debug builds) followed by the
creation of a new 9-patch structure in memory. The
Res_png_9patch::deserialize function initializes xDivsOffset,
yDivsOffset and colorsOffset using the non-zero numXDivs, numYDivs and
numColors values supplied earlier. Finally, the fileToDevice function
is called:

void Res_png_9patch::fileToDevice()
{
    int32_t* xDivs = getXDivs();
    for (int i = 0; i < numXDivs; i++) {
        xDivs[i] = ntohl(xDivs[i]);
    }
...

The getXDivs() function uses the invalid xDivsOffset value and returns
an out-of-bounds pointer value. The loop will subsequently trigger a
number of out-of-bounds ntohl() calls on heap memory, effectively
reversing the byte order of each successive 32-bit values after the
end of the allocated chunk.

Exploitation could involve making function pointers or other important
structure pointers point to invalid or attacker-controlled memory on
32-bit systems, manipulating heap meta-data to make chunks appear
larger than they are (which would induce further corruption upon that
chunk being freed and reallocated), or targeting an
application-specific size or offset field to induce some other
out-of-bounds condition that allows for greater control of the address
space.

A crafted PNG file (nein3.png) that triggers heap corruption on AOSP
master and a Nexus 7 device running 5.0.2 is attached. SIGSEGV was
observed via the Gmail app, and a custom application that simply calls
BitmapFactory.decodeStream on an arbitrary InputStream. Depending on
heap layout, multiple trigger attempts may be necessary to observe a
crash.

At least two distinct code changes are required to resolve this issue
- fixing the serializedSize return value, and enforcing that the
calculated length matches the length of the PNG chunk as provided by
libpng. Sample patches have been 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 27 Jan 2015 at 2:08

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/+/a730ef3f77fc495b
c90199b4d45efab26d609782

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