flawopen.com/Teardowns/cve-2015-3824-android-stagefright-tx3g-integer-overflow

● CVE-2015-3824 · CVSS v2 10.0 · High
FlawOpen Security Research

CVE-2015-3824: Stagefright tx3g Integer Overflow in Android libstagefright

How an unchecked size addition while parsing the MPEG-4 tx3g subtitle atom wrapped around, allocated a tiny heap buffer and let a crafted video corrupt mediaserver memory without any user interaction.

💡 Plain English Explainer (ELI5)

Imagine a print shop whose order form only has four boxes for the length of a banner. You already have 9,998 metres on file and ask to add 5 more. The clerk writes the total in the four boxes, it rolls over to 0003, and the shop cuts a 3-metre sheet. Then the printer runs the full 10,003 metres of text onto that sheet, and the ink pours over the next customer's orders on the table. Stagefright did the same with memory: the size total rolled over, a tiny buffer was cut, and the full video data was written into it.

Core Concepts & Subsystem Terms

Stagefright (libstagefright)
Android's native media framework library that parses audio and video files, running inside the privileged mediaserver process.
MPEG-4 atom
A self-describing block in an MP4 file: a declared size, a four-character type and a payload. Parsers trust the declared size to decide how much to read.
tx3g
The 3GPP timed-text atom that carries subtitle formatting data for a text track.
Integer overflow
When an arithmetic result is larger than the integer type can hold and wraps around to a small value, such as SIZE_MAX + 2 becoming 1.

Root Cause Analysis

MPEG4Extractor::parseChunk() added the size of the stored text-format data to the attacker-controlled chunk_size of a tx3g atom without checking for overflow. The wrapped sum sized the heap allocation, while readAt() then copied the full chunk_size bytes into it.

Step-by-Step Attack Flow

Step 1

A crafted video is delivered

The attacker sends an MP4 file whose tx3g atom declares a chunk_size close to the maximum value, for example by MMS or a messaging app.

Step 2

Parsing starts automatically

On Android 5.1 and earlier, the media service parsed incoming media to build previews and thumbnails, so no tap was required.

Step 3

The size calculation wraps

size + chunk_size overflows, and new[] allocates a buffer of only a few bytes.

Step 4

The heap is overwritten

readAt() copies chunk_size bytes into the small buffer, corrupting the mediaserver heap and giving the attacker code execution with its privileges.

Source Code: Flaw vs. Secure Implementation

UNPATCHED FLAW
// Simplified from MPEG4Extractor::parseChunk() in libstagefright, before the fix
case FOURCC('t', 'x', '3', 'g'):
{
    uint32_t type;
    const void *data;
    size_t size = 0;
    if (!mLastTrack->meta->findData(kKeyTextFormatData, &type, &data, &size)) {
        size = 0;
    }

    // chunk_size is read from the file. size + chunk_size can wrap around,
    // so a tiny buffer is allocated for a huge copy.
    uint8_t *buffer = new (std::nothrow) uint8_t[size + chunk_size];
    if (buffer == NULL) {
        return ERROR_MALFORMED;
    }
    if (size > 0) {
        memcpy(buffer, data, size);
    }
    // Copies the full chunk_size bytes into the undersized buffer.
    if ((size_t)mDataSource->readAt(*offset, buffer + size, chunk_size) < chunk_size) {
        delete[] buffer;
        return ERROR_IO;
    }
    mLastTrack->meta->setData(kKeyTextFormatData, 0, buffer, size + chunk_size);
    delete[] buffer;
    *offset += chunk_size;
    break;
}
HARDENED SECURE PATCH
// Simplified from MPEG4Extractor::parseChunk() in libstagefright, after the fix
case FOURCC('t', 'x', '3', 'g'):
{
    uint32_t type;
    const void *data;
    size_t size = 0;
    if (!mLastTrack->meta->findData(kKeyTextFormatData, &type, &data, &size)) {
        size = 0;
    }

    // Reject any length whose sum would overflow before allocating.
    if (chunk_size < 0 || (uint64_t)chunk_size > SIZE_MAX - size) {
        return ERROR_MALFORMED;
    }

    uint8_t *buffer = new (std::nothrow) uint8_t[size + chunk_size];
    if (buffer == NULL) {
        return ERROR_MALFORMED;
    }
    if (size > 0) {
        memcpy(buffer, data, size);
    }
    if ((size_t)mDataSource->readAt(*offset, buffer + size, chunk_size) < chunk_size) {
        delete[] buffer;
        return ERROR_IO;
    }
    mLastTrack->meta->setData(kKeyTextFormatData, 0, buffer, size + chunk_size);
    delete[] buffer;
    *offset += chunk_size;
    break;
}

Engineering & System Hardening Checklist

Sources