flawopen.com/Teardowns/cve-2015-3824-android-stagefright-tx3g-integer-overflow
MPEG-4 の字幕アトム tx3g の解析中に、検証のないサイズ加算が桁あふれしてごく小さなヒープバッファを確保し、細工された動画がユーザー操作なしで mediaserver のメモリを破壊できた仕組み。
横断幕の長さを書く欄が4桁しかない印刷所の注文票を想像してください。すでに 9998 メートルが記録されていて、さらに 5 メートル追加を頼みます。店員が合計を4桁の欄に書くと数字が一周して 0003 になり、印刷所は 3 メートルの紙を切り出します。ところが印刷機は 10003 メートル分の文字をその紙に刷り続け、インクが机の上にある他の客の注文にまであふれ出します。Stagefright はメモリで同じことをしました。サイズの合計が一周して小さなバッファだけが確保され、そこに動画のデータがすべて書き込まれたのです。
Stagefright(libstagefright)MPEG-4 アトムtx3g整数オーバーフローMPEG4Extractor::parseChunk() は、保存済みの書式データのサイズを、攻撃者が制御できる tx3g アトムの chunk_size に加算する際にオーバーフローを確認していなかった。一周した合計値でヒープ確保のサイズが決まり、その後 readAt() が chunk_size バイトすべてをそこへコピーした。
攻撃者は tx3g アトムの chunk_size に最大値付近の値を宣言した MP4 ファイルを、MMS やメッセージアプリで送りつける。
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 を有効にして、メディアやファイルのパーサーを継続的にファジングする。