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

● CVE-2015-3824 · CVSS v2 10.0 · Высокий
Исследования · FlawOpen

CVE-2015-3824: целочисленное переполнение tx3g в Stagefright (libstagefright Android)

Как непроверенное сложение размеров при разборе атома субтитров tx3g формата MPEG-4 переполнилось, выделило крошечный буфер в куче и позволило специально подготовленному видео повредить память mediaserver без каких-либо действий пользователя.

💡 Простыми словами (ELI5)

Представьте типографию, в бланке заказа которой для длины баннера есть всего четыре клетки. У вас уже записано 9 998 метров, и вы просите добавить ещё 5. Сотрудник вписывает сумму в четыре клетки, число превращается в 0003, и типография отрезает лист длиной 3 метра. Затем машина печатает на этом листе все 10 003 метра текста, и краска растекается по заказам других клиентов на столе. Stagefright поступал так же с памятью: сумма размеров переполнилась, выделился крошечный буфер, и в него записались все данные видео.

Ключевые понятия и термины

Stagefright (libstagefright)
Нативная мультимедийная библиотека Android, разбирающая аудио- и видеофайлы внутри привилегированного процесса mediaserver.
Атом MPEG-4
Самоописывающий блок файла MP4: заявленный размер, тип из четырёх символов и данные. Парсер доверяет заявленному размеру, решая, сколько байт прочитать.
tx3g
Атом синхронизированного текста 3GPP, в котором хранится оформление субтитров текстовой дорожки.
Целочисленное переполнение
Ситуация, когда результат вычисления не помещается в целочисленный тип и «заворачивается» в маленькое значение, например SIZE_MAX + 2 превращается в 1.

Анализ первопричины

MPEG4Extractor::parseChunk() прибавляла размер уже сохранённых данных оформления к управляемому атакующим chunk_size атома tx3g без проверки на переполнение. Переполнившаяся сумма задавала размер выделяемой памяти в куче, после чего readAt() копировала туда все chunk_size байт.

Пошаговый сценарий атаки

Step 1

Доставляется подготовленное видео

Атакующий отправляет MP4-файл, атом tx3g которого заявляет chunk_size, близкий к максимальному значению, например через MMS или мессенджер.

Step 2

Разбор запускается автоматически

В Android 5.1 и более ранних версиях медиасервис разбирал входящие файлы для создания превью и миниатюр — нажимать ничего не требовалось.

Step 3

Вычисление размера переполняется

size + chunk_size переполняется, и new[] выделяет буфер всего в несколько байт.

Step 4

Куча перезаписывается

readAt() копирует chunk_size байт в маленький буфер, повреждая кучу mediaserver и давая атакующему выполнение кода с правами этого процесса.

Исходный код: Уязвимый vs Защищённый вариант

УЯЗВИМАЯ РЕАЛИЗАЦИЯ
// 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;
}

Чек-лист по защите системы для инженеров

Источники