flawopen.com/Teardowns/cve-2015-3824-android-stagefright-tx3g-integer-overflow
MPEG-4 자막 아톰 tx3g를 파싱하는 중 검사하지 않은 크기 덧셈이 한 바퀴 돌아 아주 작은 힙 버퍼를 할당했고, 조작된 동영상이 사용자 조작 없이 mediaserver의 메모리를 손상시킬 수 있었던 방식.
현수막 길이를 적는 칸이 네 자리밖에 없는 인쇄소 주문서를 떠올려 보세요. 이미 9,998미터가 기록되어 있는데 5미터를 더 주문합니다. 직원이 합계를 네 칸에 적자 숫자가 한 바퀴 돌아 0003이 되고, 인쇄소는 3미터짜리 종이를 자릅니다. 그런데 인쇄기는 10,003미터 분량의 글자를 그 종이에 계속 찍어 내고, 잉크가 책상 위 다른 손님들의 주문서까지 번집니다. 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를 켠 상태로 미디어·파일 파서를 지속적으로 퍼징한다.