
Certains bugs ont été découverts via l'instrumentation binaire et le fuzzing
media-gen est un petit outil en ligne de commande Rust, léger en dépendances, destiné à construire deux cas de test pour analyseur multimédia. Il modifie sur place des fichiers multimédias valides existants ; il n'invoque pas FFmpeg, ne patche pas un navigateur, ne contacte aucun serveur et ne téléverse rien.
Le cas WebM inclut une disposition de pont pour l'ancien chemin Vorbis à un tampon de retard et pour le chemin actuel de rejet immédiat. L'échantillon court versionné a été validé avec Discord Desktop 1.0.9257 (Electron 42.11.1 / Chromium 148.0.7778.280) et Chrome 153.0.8010.48. Le cas M4A reste spécifique à la version figée de Discord/FFmpeg. D'autres versions peuvent rejeter ces fichiers, les traiter en toute sécurité ou échouer différemment.
| Cas | Entrée | Effet dans la version affectée | Déclencheur |
|---|
| Pont de rejet WebM/Vorbis | WebM existant avec une piste A_VORBIS | Le moteur de rendu se termine avec 0x80000003 (STATUS_BREAKPOINT) dans la vérification de version de AudioDiscardHelper de Chromium sur les deux modes de rejet testés | Lecture/décodage ; le chargement des métadonnées seul ne suffit pas |
Compteur stsz constant M4A | Graine AAC/M4A fast-start existante | Le moteur de rendu alloue transitoirement environ 6,6 Go (6,16 Gio) de mémoire privée pendant le chargement des métadonnées | Chargement des métadonnées après que l'élément audio passe de preload="none" aux métadonnées |
Il s'agit de cas de test de déni de service/consommation de ressources, et non d'exploits d'exécution de code démontrés. Exécutez-les uniquement dans un environnement de test isolé et limité.
media-gen est la couche de reproductibilité, et non la manière dont ces cas ont été découverts. La partie difficile a été de trouver et de prouver le comportement à l'intérieur d'un grand exécutable natif Discord et de ses bibliothèques multimédias embarquées. blare2 a rendu cela praticable en réécrivant une copie isolée de l'environnement d'exécution exact et en permettant à des sondes étroites d'observer l'exécution sans modifier l'application installée.
Pour le cas WebM, la sonde sémantique de blare2 s'est arrêtée exactement à la vérification AudioDiscardHelper::ProcessBuffers et a enregistré l'état défaillant : discarded_frames = 129 et decoder_delay = 128. Sans cette observation, une sortie du moteur de rendu avec STATUS_BREAKPOINT n'identifierait qu'une assertion de version générique ; elle n'établirait pas quel invariant multimédia a échoué ni si le remplissage fabriqué l'a réellement atteint.
Pour le cas M4A, les grandes allocations sont transitoires et peuvent disparaître avant qu'un échantillon de processus normal ne soit prélevé. La couverture exacte de l'environnement d'exécution et l'instrumentation ciblée de blare2, combinées à un échantillonnage haute fréquence des processus et au désassemblage, ont montré que le minuscule fichier atteignait le constructeur de table d'échantillons MOV et que le compteur déclaré dimensionnait les allocations d'AVIndexEntry et de table de timing. Cela a permis de distinguer le problème d'un échec de décodage AAC ordinaire ou d'une corrélation trompeuse taille de fichier/OOM. La couverture étendue des entrées de fonctions seule ne suffisait pas : les fichiers à compteur bas et élevé suivaient les mêmes fonctions, tandis que leurs tailles d'allocation étaient radicalement différentes.
blare2 n'a pas remplacé l'analyse de conteneur, la revue de code source ou les contrôles, et il n'a pas prouvé que le chemin de production de téléversement/CDN de Discord préserve ces octets. Sa valeur résidait dans l'attribution à l'exécution : il a transformé un comportement d'analyseur suspect en une découverte reproductible, figée sur une version, avec un état défaillant exact et une explication d'allocation défendable. Sans ce type d'instrumentation binaire, trouver ces deux cas aurait été sensiblement plus lent et bien plus difficile à valider.
Depuis la racine du dépôt :
cargo build --release -p media-gen
Le binaire est target/release/media-gen (media-gen.exe sous Windows). Le générateur lui-même n'a besoin que de Rust et des dépendances dans Cargo.toml.
cargo run --release -p media-gen -- --help
cargo run --release -p media-gen -- webm --help
cargo run --release -p media-gen -- m4a --help
Un DiscardPadding négatif Matroska devient un saut avant Vorbis. L'ancien chemin Vorbis de Chromium retarde ces métadonnées d'un paquet encodé ; le Chromium actuel les applique à la sortie décodée courante et abandonne les métadonnées du paquet 0 lorsque ce paquet d'amorçage n'émet aucun PCM. Répéter un grand saut sur les paquets 0 et 1 fait le pont entre les deux comportements :
| Paquet audio | PCM décodé | Saut avant |
|---|---|---|
| 0 | aucun (amorçage Vorbis) | 577 trames |
| 1 | 576 trames | 577 trames |
| 2 | 1 024 trames | 1 trame |
La piste a un CodecDelay de 128 trames. Dans l'ancien mode retardé, le saut de 577 trames du paquet 0 est appliqué au paquet 1. Dans le mode actuel, le saut du paquet 0 est abandonné et le saut identique du paquet 1 est appliqué directement. Dans les deux cas, seules 448 trames peuvent être retirées après le décalage de retard du décodeur, donc 129 trames sont reportées dans le paquet 2. Son saut avant positif atteint la vérification de version de Chromium après que ces 129 trames ont déjà été retirées. L'invariant requis est discarded_frames <= decoder_delay ; 129 <= 128 échoue et termine le moteur de rendu.
Le générateur analyse les en-têtes de configuration Vorbis, calcule les tailles décodées des paquets 1 et 2, et échoue en mode fermé à moins que le pont ne soit viable. Il effectue ensuite :
SimpleBlock en éléments BlockGroup ;DiscardPadding négatif partagé sur les paquets 0 et 1 et un déclencheur positif d'une trame sur le paquet 2 ;SeekHead, Cues et les CRC de cluster affectés devenus obsolètes afin que le conteneur réécrit reste analysable.Le manifeste rapporte séparément les chemins immédiat et retardé. Pour l'échantillon versionné, les deux chemins désignent le paquet 2 comme paquet de vérification et rapportent expected_carry_frames: 129 et expected_check_fails: true.
Le dépôt contient un WebM de contrôle de 43 ms et 4 185 octets :
cargo run --release -- webm \
--input samples/short-vorbis-dual-control.webm \
--output .build/short-vorbis-dual-crash.webm \
--manifest .build/short-vorbis-dual-crash.json \
--force
L'entrée doit contenir :
A_VORBIS ;CodecDelay positif (normalement lu depuis la piste) ; etLes options utiles sont --trigger-skip, --codec-delay et --sample-rate. --second-skip reste un alias pour l'option renommée --trigger-skip. Les valeurs par défaut sont celles nécessaires à l'état de défaillance connu. La commande imprime le rapport JSON sur stdout et écrit le même rapport dans --manifest lorsque cette option est fournie.
Le candidat versionné fait 43 ms et 4 206 octets. Son SHA-256 est :
a0ab9e146c629f037b86612addc1ab6fff9200711d45aa5f5edbb9576cc206ac
Le hachage de sortie est censé changer si l'entrée, la fenêtre de paquets ou les options changent.
Le cas M4A abuse d'une forme valide de table d'échantillons MP4 plutôt que de la charge utile AAC encodée. Le générateur modifie une graine comme suit :
stsz par sample_size = 1.stsz, la première série stsc et la première série stts à la même grande valeur.stco afin que la charge utile AAC d'un octet pointe toujours à l'intérieur de mdat.Le démultiplexeur FFmpeg figé traite le compteur déclaré comme faisant autorité lors de la construction de ses tables d'index et de timing. Les structures concernées utilisent environ 24 octets par échantillon déclaré pour AVIndexEntry et 12 octets par échantillon pour les données de timing : 36 octets par entrée de compteur au total. Le compteur maximal accepté dans la version testée est 178,956,969 (0x0AAAAAA9), ce qui correspond à :
178,956,969 * 24 = 4,294,967,256 bytes
178,956,969 * 12 = 2,147,483,628 bytes
combined = 6,442,450,884 bytes
Le moteur de rendu Discord exact a atteint un pic de mémoire privée de 6 614 761 472 octets pendant le chargement des métadonnées. Le compteur adjacent 178,956,970 est rejeté par la limite FFmpeg figée et est resté proche d'une mémoire normale. Il s'agit d'une consommation de ressources non contrôlée (CWE-400), et non d'un débordement d'entier observé, d'une allocation de taille négative ou d'une écriture hors limites. La charge utile AAC réelle peut être tronquée ; une lecture réussie n'est pas nécessaire pour atteindre la grande allocation.
Le générateur accepte intentionnellement une graine au lieu d'intégrer un encodeur AAC. Utilisez un fichier AAC/M4A fast-start avec une table stsz explicite, une série stsc, une ou plusieurs entrées stts et des offsets de chunk stco. Il échoue en mode fermé si la structure requise est absente.
cargo run --release -p media-gen -- m4a \
--input path/to/base-faststart.m4a \
--output .build/media-gen/constant-stsz.m4a \
--manifest .build/media-gen/constant-stsz.json \
--force
La valeur par défaut de --sample-count est 178956969, la valeur maximale acceptée dans la version figée. Utilisez une valeur plus petite pour un test de fumée à faible mémoire, par exemple :
cargo run --release -p media-gen -- m4a \
--input path/to/base-faststart.m4a \
--output .build/media-gen/constant-stsz-32000000.m4a \
--sample-count 32000000 \
--force
--sample-count 178956970 est un contrôle de rejet adjacent utile, et non la valeur déclenchante. L'option facultative --moov-at-end déplace moov après mdat et met à jour stco/co64 ; elle est utile pour tester un fichier dont les octets AAC physiques précèdent ses métadonnées, mais elle n'est pas requise pour le mécanisme d'allocation.
Pour produire explicitement cette disposition :
cargo run --release -p media-gen -- m4a \
--input path/to/base-faststart.m4a \
--output .build/media-gen/constant-stsz-moov-end.m4a \
--moov-at-end \
--force
Les artefacts correspondants sont
samples/short-vorbis-dual-control.webm,
samples/short-vorbis-dual-crash.webm,
et samples/short-vorbis-dual-crash.json.