
Alcuni bug trovati tramite strumentazione binaria e fuzzing
media-gen è un piccolo strumento a riga di comando in Rust, con poche dipendenze, per costruire due casi di test per parser multimediali. Modifica sul posto file multimediali validi esistenti; non invoca FFmpeg, non applica patch a un browser, non contatta un server e non carica nulla.
Il caso WebM include un layout bridge sia per il vecchio percorso Vorbis con un buffer di ritardo sia per l'attuale percorso di scarto immediato. Il breve campione incluso è stato validato con Discord Desktop 1.0.9257 (Electron 42.11.1 / Chromium 148.0.7778.280) e Chrome 153.0.8010.48. Il caso M4A rimane specifico per la build Discord/FFmpeg fissata. Altre release possono rifiutare questi file, gestirli in modo sicuro o fallire diversamente.
| Caso | Input | Effetto nella build interessata | Trigger |
|---|
| Bridge di scarto WebM/Vorbis | WebM esistente con una traccia A_VORBIS | Il renderer termina con 0x80000003 (STATUS_BREAKPOINT) nel controllo di release di AudioDiscardHelper di Chromium su entrambe le modalità di scarto testate | Riproduzione/decodifica; il solo caricamento dei metadati non è sufficiente |
Conteggio stsz costante M4A | Seed AAC/M4A fast-start esistente | Il renderer alloca transitoriamente circa 6,6 GB (6,16 GiB) di memoria privata durante il caricamento dei metadati | Caricamento dei metadati dopo che l'elemento audio passa da preload="none" a metadata |
Questi sono casi di test di denial-of-service/consumo di risorse, non exploit di esecuzione di codice dimostrati. Eseguirli solo in un ambiente di test isolato e limitato.
media-gen è il livello di riproducibilità, non il modo in cui questi casi sono stati scoperti. La parte difficile è stata trovare e dimostrare il comportamento all'interno di un grande eseguibile nativo Discord e delle sue librerie multimediali incluse. blare2 lo ha reso praticabile riscrivendo una copia isolata dell'esatto runtime e consentendo a sonde mirate di osservare l'esecuzione senza modificare l'applicazione installata.
Per il caso WebM, la sonda semantica di blare2 si è fermata esattamente al controllo AudioDiscardHelper::ProcessBuffers e ha registrato lo stato di fallimento: discarded_frames = 129 e decoder_delay = 128. Senza quell'osservazione, un'uscita del renderer con STATUS_BREAKPOINT avrebbe identificato solo un'asserzione di release generica; non avrebbe stabilito quale invariante multimediale fosse fallita né se il padding costruito l'avesse effettivamente raggiunta.
Per il caso M4A, le grandi allocazioni sono transitorie e possono scomparire prima che venga prelevato un normale campione di processo. La copertura dell'esatto runtime e la strumentazione mirata di blare2, combinate con campionamento di processo ad alta frequenza e disassemblaggio, hanno mostrato che il file minuscolo raggiungeva il costruttore della tabella dei campioni MOV e che il conteggio dichiarato scalava le allocazioni di AVIndexEntry e della tabella temporale. Questo ha separato il problema da un ordinario fallimento di decodifica AAC o da una fuorviante correlazione dimensione-file/OOM. La sola copertura ampia delle entry di funzione non era sufficiente: i file a conteggio basso e alto seguivano le stesse funzioni, mentre le loro dimensioni di allocazione erano radicalmente diverse.
blare2 non ha sostituito l'analisi del container, la revisione del codice sorgente o i controlli, e non ha dimostrato che il percorso di upload/CDN di produzione di Discord preservi questi byte. Il suo valore è stata l'attribuzione a runtime: ha trasformato un comportamento sospetto del parser in una scoperta riproducibile, con versione fissata, con uno stato di fallimento esatto e una spiegazione difendibile dell'allocazione. Senza questo tipo di strumentazione binaria, trovare questi due casi sarebbe stato sostanzialmente più lento e molto più difficile da validare.
Dalla radice del repository:
cargo build --release -p media-gen
Il binario è target/release/media-gen (media-gen.exe su Windows). Il generatore stesso necessita solo di Rust e delle dipendenze in 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 negativo Matroska diventa un front skip Vorbis. Il vecchio percorso Vorbis di Chromium ritarda quei metadati di un pacchetto codificato; il Chromium attuale li applica all'output decodificato corrente e scarta i metadati del pacchetto 0 quando quel pacchetto di priming non emette PCM. Ripetere un grande skip sui pacchetti 0 e 1 collega i due comportamenti:
| Pacchetto audio | PCM decodificato | Front skip |
|---|---|---|
| 0 | nessuno (priming Vorbis) | 577 frame |
| 1 | 576 frame | 577 frame |
| 2 | 1.024 frame | 1 frame |
La traccia ha un CodecDelay di 128 frame. Nella vecchia modalità ritardata, lo skip di 577 frame del pacchetto 0 viene applicato al pacchetto 1. Nella modalità attuale, lo skip del pacchetto 0 viene scartato e lo skip identico del pacchetto 1 viene applicato direttamente. In entrambi i casi, solo 448 frame possono essere rimossi dopo l'offset del decoder-delay, quindi 129 frame passano al pacchetto 2. Il suo front skip positivo raggiunge il controllo di release di Chromium dopo che quei 129 frame sono già stati rimossi. L'invariante richiesta è discarded_frames <= decoder_delay; 129 <= 128 fallisce e termina il renderer.
Il generatore analizza gli header di setup Vorbis, calcola le dimensioni decodificate dei pacchetti 1 e 2 e fallisce in modo sicuro a meno che il bridge sia praticabile. Poi:
SimpleBlock in elementi BlockGroup;DiscardPadding negativo condiviso sui pacchetti 0 e 1 e un trigger positivo di un frame sul pacchetto 2;SeekHead, Cues e i CRC dei cluster interessati obsoleti, così che il container riscritto rimanga analizzabile.Il manifest riporta separatamente i percorsi immediato e ritardato. Per il campione incluso, entrambi i percorsi indicano il pacchetto 2 come pacchetto di controllo e riportano expected_carry_frames: 129 e expected_check_fails: true.
Il repository contiene un WebM di controllo di 43 ms e 4.185 byte:
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'input deve contenere:
A_VORBIS;CodecDelay positivo (normalmente letto dalla traccia); eOpzioni utili sono --trigger-skip, --codec-delay e --sample-rate. --second-skip rimane un alias per l'opzione rinominata --trigger-skip. I valori predefiniti sono quelli necessari per lo stato di fallimento noto. Il comando stampa il report JSON su stdout e scrive lo stesso report in --manifest quando tale opzione è fornita.
Il candidato incluso è di 43 ms e 4.206 byte. Il suo SHA-256 è:
a0ab9e146c629f037b86612addc1ab6fff9200711d45aa5f5edbb9576cc206ac
Ci si aspetta che l'hash di output cambi se cambiano l'input, la finestra dei pacchetti o le opzioni.
Il caso M4A sfrutta una forma valida della tabella dei campioni MP4 anziché il payload AAC codificato. Il generatore modifica un seed come segue:
stsz con sample_size = 1.stsz, il primo run stsc e il primo run stts allo stesso grande valore.stco in modo che il payload AAC di un byte punti ancora all'interno di mdat.Il demuxer FFmpeg fissato tratta il conteggio dichiarato come autorevole mentre costruisce le sue tabelle di indice e temporali. Le strutture rilevanti usano circa 24 byte per campione dichiarato per AVIndexEntry e 12 byte per campione per i dati temporali: 36 byte per voce di conteggio in totale. Il conteggio massimo accettato nella build testata è 178.956.969 (0x0AAAAAA9), che proietta a:
178,956,969 * 24 = 4,294,967,256 bytes
178,956,969 * 12 = 2,147,483,628 bytes
combined = 6,442,450,884 bytes
L'esatto renderer Discord ha raggiunto un picco di memoria privata di 6.614.761.472 byte durante il caricamento dei metadati. Il conteggio adiacente 178.956.970 viene rifiutato dal confine FFmpeg fissato ed è rimasto vicino alla memoria normale. Questo è consumo incontrollato di risorse (CWE-400), non un integer-wrap osservato, un'allocazione a dimensione negativa o una scrittura fuori dai limiti. Il payload AAC effettivo può essere troncato; una riproduzione riuscita non è necessaria per raggiungere la grande allocazione.
Il generatore accetta intenzionalmente un seed invece di incorporare un encoder AAC. Usa un file AAC/M4A fast-start con una tabella stsz esplicita, un run stsc, una o più voci stts e offset di chunk stco. Fallisce in modo sicuro se la struttura richiesta è assente.
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
Il valore predefinito di --sample-count è 178956969, il valore massimo accettato nella build fissata. Usa un valore più piccolo per uno smoke test a bassa memoria, ad esempio:
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 è un utile controllo di rifiuto adiacente, non il valore che innesca il problema. Il flag opzionale --moov-at-end sposta moov dopo mdat e aggiorna stco/co64; è utile quando si testa un file i cui byte AAC fisici si trovano prima dei suoi metadati, ma non è richiesto per il meccanismo di allocazione.
Per produrre esplicitamente quel layout:
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
Gli artefatti corrispondenti sono
samples/short-vorbis-dual-control.webm,
samples/short-vorbis-dual-crash.webm,
e samples/short-vorbis-dual-crash.json.