flawopen.com/Teardowns/cve-2015-3824-android-stagefright-tx3g-integer-overflow
在解析 MPEG-4 字幕原子 tx3g 时,一次未经检查的大小相加发生回绕,只分配了极小的堆缓冲区,使精心构造的视频无需任何用户操作即可破坏 mediaserver 的内存。
想象一家印刷店,订单表上只有四个格子用来填写横幅长度。档案里已经记了 9998 米,你又要求再加 5 米。店员把总数写进四个格子,数字回绕成了 0003,于是店里只裁了一张 3 米长的纸。接着印刷机把整整 10003 米的文字印到这张纸上,油墨溢出来弄脏了桌上其他顾客的订单。Stagefright 对内存做的就是同样的事:大小之和发生回绕,只分配了一个极小的缓冲区,却把全部视频数据写了进去。
Stagefright(libstagefright)MPEG-4 原子(atom)tx3g整数溢出MPEG4Extractor::parseChunk() 将已保存的文本格式数据大小与攻击者可控的 tx3g 原子 chunk_size 相加,却没有检查溢出。回绕后的和决定了堆分配的大小,随后 readAt() 把完整的 chunk_size 字节复制进去。
攻击者通过彩信或即时通讯应用发送一个 MP4 文件,其 tx3g 原子声明的 chunk_size 接近最大值。
在 Android 5.1 及更早版本中,媒体服务会自动解析收到的媒体以生成预览和缩略图,无需用户点击。
size + chunk_size 溢出,new[] 只分配了几个字节的缓冲区。
readAt() 将 chunk_size 字节复制进这个小缓冲区,破坏 mediaserver 的堆,使攻击者以该进程的权限执行代码。
// 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 的情况下对媒体和文件解析器持续进行模糊测试。