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

● CVE-2015-3824 · CVSS v2 10.0 · Hoch
Sicherheitsforschung · FlawOpen

CVE-2015-3824: tx3g-Integer-Überlauf in Stagefright (Android libstagefright)

Wie eine ungeprüfte Größenaddition beim Parsen des MPEG-4-Untertitel-Atoms tx3g überlief, einen winzigen Heap-Puffer anlegte und es einem präparierten Video erlaubte, den Speicher von mediaserver ohne jede Nutzerinteraktion zu beschädigen.

💡 Einfache Erklärung (ELI5)

Stellen Sie sich eine Druckerei vor, deren Bestellformular nur vier Kästchen für die Länge eines Banners hat. Es sind bereits 9.998 Meter eingetragen, und Sie bestellen 5 weitere. Der Angestellte trägt die Summe in die vier Kästchen ein, die Zahl springt auf 0003, und die Druckerei schneidet ein 3-Meter-Blatt zu. Dann druckt die Maschine die vollen 10.003 Meter Text auf dieses Blatt, und die Tinte läuft über die Aufträge der anderen Kunden auf dem Tisch. Stagefright tat dasselbe mit dem Speicher: Die Summe lief über, ein winziger Puffer wurde angelegt, und alle Videodaten wurden hineingeschrieben.

Kernkonzepte & Begriffe

Stagefright (libstagefright)
Androids native Medienbibliothek, die Audio- und Videodateien im privilegierten Prozess mediaserver parst.
MPEG-4-Atom
Ein selbstbeschreibender Block einer MP4-Datei: eine angegebene Größe, ein Typ aus vier Zeichen und die Nutzdaten. Der Parser vertraut der angegebenen Größe, um zu entscheiden, wie viel er liest.
tx3g
Das 3GPP-Atom für zeitgesteuerten Text, das die Formatierung der Untertitel einer Textspur enthält.
Integer-Überlauf
Wenn ein Rechenergebnis größer ist, als der Ganzzahltyp fassen kann, und auf einen kleinen Wert zurückspringt, etwa SIZE_MAX + 2, das zu 1 wird.

Ursachenanalyse

MPEG4Extractor::parseChunk() addierte die Größe der bereits gespeicherten Formatdaten zum vom Angreifer kontrollierten chunk_size des tx3g-Atoms, ohne auf Überlauf zu prüfen. Die übergelaufene Summe bestimmte die Größe der Heap-Allokation, und readAt() kopierte anschließend die vollen chunk_size Bytes hinein.

Schritt-für-Schritt Angriffsablauf

Step 1

Ein präpariertes Video wird zugestellt

Der Angreifer schickt eine MP4-Datei, deren tx3g-Atom einen chunk_size nahe am Maximalwert angibt, etwa per MMS oder Messenger.

Step 2

Das Parsen startet von selbst

Unter Android 5.1 und älter parste der Mediendienst eingehende Medien, um Vorschauen und Miniaturansichten zu erzeugen, ganz ohne Antippen.

Step 3

Die Größenberechnung läuft über

size + chunk_size läuft über, und new[] legt nur einen Puffer von wenigen Bytes an.

Step 4

Der Heap wird überschrieben

readAt() kopiert chunk_size Bytes in den kleinen Puffer, beschädigt den Heap von mediaserver und verschafft dem Angreifer Codeausführung mit dessen Rechten.

Quellcode: Verwundbar vs. Sicher

VERWUNDBARE IMPLEMENTIERUNG
// 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;
}
GEHÄRTETER SICHERHEITS-PATCH
// 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;
}

Checkliste für Engineering & Systemsicherheit

Quellen