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

● CVE-2015-3824 · CVSS v2 10.0 · Haute
Recherche · FlawOpen

CVE-2015-3824 : dépassement d'entier tx3g dans Stagefright (libstagefright d'Android)

Comment une addition de tailles non vérifiée, lors de l'analyse de l'atome de sous-titres tx3g du MPEG-4, a débordé, alloué un tampon minuscule sur le tas et permis à une vidéo piégée de corrompre la mémoire de mediaserver sans aucune interaction de l'utilisateur.

💡 Explication en Langage Simple (ELI5)

Imaginez une imprimerie dont le bon de commande n'a que quatre cases pour la longueur d'une banderole. Il y a déjà 9 998 mètres enregistrés et vous en demandez 5 de plus. L'employé écrit le total dans les quatre cases, le compteur repasse à 0003 et l'imprimerie découpe une feuille de 3 mètres. Puis la machine imprime les 10 003 mètres de texte sur cette feuille, et l'encre se répand sur les commandes des autres clients posées sur la table. Stagefright a fait de même avec la mémoire : le total a débordé, un tampon minuscule a été réservé et toutes les données de la vidéo y ont été écrites.

Concepts Clés et Termes

Stagefright (libstagefright)
Bibliothèque multimédia native d'Android qui analyse les fichiers audio et vidéo dans le processus privilégié mediaserver.
Atome MPEG-4
Bloc auto-descriptif d'un fichier MP4 : une taille déclarée, un type de quatre caractères et une charge utile. L'analyseur se fie à la taille déclarée pour savoir combien lire.
tx3g
Atome de texte synchronisé 3GPP qui contient la mise en forme des sous-titres d'une piste de texte.
Dépassement d'entier
Lorsque le résultat d'un calcul dépasse ce que le type entier peut contenir et revient à une petite valeur, par exemple SIZE_MAX + 2 qui devient 1.

Analyse de Cause Racine

MPEG4Extractor::parseChunk() ajoutait la taille des données de mise en forme déjà stockées au chunk_size de l'atome tx3g, contrôlé par l'attaquant, sans vérifier le dépassement. La somme repassée à zéro fixait la taille de l'allocation sur le tas, puis readAt() y copiait l'intégralité des chunk_size octets.

Déroulement de l'Attaque Étape par Étape

Step 1

Une vidéo piégée est envoyée

L'attaquant envoie un fichier MP4 dont l'atome tx3g déclare un chunk_size proche de la valeur maximale, par exemple par MMS ou via une messagerie.

Step 2

L'analyse démarre seule

Sous Android 5.1 et versions antérieures, le service multimédia analysait les fichiers reçus pour créer des aperçus et des miniatures, sans aucun geste de l'utilisateur.

Step 3

Le calcul de taille déborde

size + chunk_size dépasse la capacité du type et new[] n'alloue qu'un tampon de quelques octets.

Step 4

Le tas est écrasé

readAt() copie chunk_size octets dans le petit tampon, corrompt le tas de mediaserver et offre à l'attaquant l'exécution de code avec les privilèges de ce processus.

Code Source : Vulnérable vs Sécurisé

IMPLÉMENTATION VULNÉRABLE
// 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;
}
PATCH SÉCURISÉ ET ROBUSTE
// 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;
}

Liste de Contrôle de Sécurité pour l'Ingénierie

Sources