flawopen.com/Teardowns/cve-2015-3824-android-stagefright-tx3g-integer-overflow
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.
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.
Stagefright (libstagefright)MPEG-4 atomtx3gInteger overflowMPEG4Extractor::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.
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.
On Android 5.1 and earlier, the media service parsed incoming media to build previews and thumbnails, so no tap was required.
size + chunk_size overflows, and new[] allocates a buffer of only a few bytes.
readAt() copies chunk_size bytes into the small buffer, corrupting the mediaserver heap and giving the attacker code execution with its privileges.
// 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;
}
// 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;
}
fsanitize=integer enabled.