
Alguns bugs encontrados por meio de instrumentação binária e fuzzing
media-gen é uma pequena ferramenta de linha de comando em Rust, com poucas dependências, para construir dois casos de teste de parser de mídia. Ela edita arquivos de mídia válidos existentes no local; não invoca o FFmpeg, não aplica patch em um navegador, não contata um servidor nem faz upload de nada.
O caso WebM inclui um layout de ponte tanto para o caminho Vorbis mais antigo, com atraso de um buffer, quanto para o caminho atual de descarte imediato. A amostra curta incluída no repositório foi validada com o Discord Desktop 1.0.9257 (Electron 42.11.1 / Chromium 148.0.7778.280) e o Chrome 153.0.8010.48. O caso M4A permanece específico para a build fixada do Discord/FFmpeg. Outras versões podem rejeitar esses arquivos, tratá-los com segurança ou falhar de forma diferente.
| Caso | Entrada | Efeito na build afetada | Gatilho |
|---|
| Ponte de descarte WebM/Vorbis | WebM existente com uma faixa A_VORBIS | O renderer termina com 0x80000003 (STATUS_BREAKPOINT) na verificação de release do AudioDiscardHelper do Chromium, em ambos os modos de descarte testados | Reprodução/decodificação; apenas o carregamento de metadados não é suficiente |
Contagem stsz constante em M4A | Semente AAC/M4A fast-start existente | O renderer aloca transitoriamente cerca de 6,6 GB (6,16 GiB) de memória privada ao carregar metadados | Carregamento de metadados após o elemento de áudio mudar de preload="none" para metadados |
Estes são casos de teste de negação de serviço/consumo de recursos, não exploits demonstrados de execução de código. Execute-os apenas em um ambiente de teste isolado e limitado.
media-gen é a camada de reprodutibilidade, não o modo como esses casos foram descobertos. A parte difícil foi encontrar e provar o comportamento dentro de um grande executável nativo do Discord e de suas bibliotecas de mídia empacotadas. O blare2 tornou isso viável ao reescrever uma cópia isolada do runtime exato e permitir que sondagens estreitas observassem a execução sem alterar o aplicativo instalado.
Para o caso WebM, a sondagem semântica do blare2 parou exatamente na verificação AudioDiscardHelper::ProcessBuffers e registrou o estado de falha: discarded_frames = 129 e decoder_delay = 128. Sem essa observação, uma saída do renderer com STATUS_BREAKPOINT apenas identificaria uma asserção de release genérica; não estabeleceria qual invariante de mídia falhou nem se o padding construído realmente chegou até ela.
Para o caso M4A, as grandes alocações são transitórias e podem desaparecer antes que uma amostra normal do processo seja coletada. A cobertura do runtime exato e a instrumentação direcionada do blare2, combinadas com amostragem de processo em alta frequência e desmontagem, mostraram que o arquivo minúsculo chegou ao construtor da tabela de amostras MOV e que a contagem declarada escalou as alocações de AVIndexEntry e da tabela de temporização. Isso separou o problema de uma falha comum de decodificação AAC ou de uma correlação enganosa entre tamanho de arquivo e OOM. A cobertura ampla de entrada de funções por si só não foi suficiente: os arquivos de contagem baixa e alta seguiram as mesmas funções, enquanto seus tamanhos de alocação eram radicalmente diferentes.
O blare2 não substituiu a análise de contêiner, a revisão de código-fonte ou os controles, e não provou que o caminho de upload/CDN de produção do Discord preserva esses bytes. Seu valor foi a atribuição em tempo de execução: transformou um comportamento suspeito do parser em um achado reproduzível, fixado por versão, com um estado de falha exato e uma explicação defensável de alocação. Sem instrumentação binária desse tipo, encontrar esses dois casos teria sido substancialmente mais lento e muito mais difícil de validar.
A partir da raiz do repositório:
cargo build --release -p media-gen
O binário é target/release/media-gen (media-gen.exe no Windows). O próprio gerador precisa apenas de Rust e das dependências em 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
Um DiscardPadding negativo do Matroska se torna um front skip do Vorbis. O caminho Vorbis mais antigo do Chromium atrasa esses metadados em um pacote codificado; o Chromium atual os aplica à saída decodificada atual e descarta os metadados do pacote 0 quando esse pacote de priming não emite PCM. Repetir um grande skip nos pacotes 0 e 1 faz a ponte entre os dois comportamentos:
| Pacote de áudio | PCM decodificado | Front skip |
|---|---|---|
| 0 | nenhum (priming do Vorbis) | 577 quadros |
| 1 | 576 quadros | 577 quadros |
| 2 | 1.024 quadros | 1 quadro |
A faixa tem um CodecDelay de 128 quadros. No modo atrasado mais antigo, o skip de 577 quadros do pacote 0 é aplicado ao pacote 1. No modo atual, o skip do pacote 0 é descartado e o skip idêntico do pacote 1 é aplicado diretamente. De qualquer forma, apenas 448 quadros podem ser removidos após o deslocamento do decoder-delay, então 129 quadros passam para o pacote 2. Seu front skip positivo chega à verificação de release do Chromium depois que esses 129 quadros já foram removidos. O invariante exigido é discarded_frames <= decoder_delay; 129 <= 128 falha e encerra o renderer.
O gerador analisa os cabeçalhos de setup do Vorbis, calcula os tamanhos decodificados dos pacotes 1 e 2 e falha de forma segura a menos que a ponte seja viável. Em seguida, ele:
SimpleBlock de áudio em elementos BlockGroup;DiscardPadding negativo compartilhado nos pacotes 0 e 1 e um gatilho positivo de um quadro no pacote 2;SeekHead, Cues e CRC de clusters afetados para que o contêiner reescrito permaneça analisável.O manifesto relata os caminhos imediato e atrasado separadamente. Para a amostra incluída no repositório, ambos os caminhos indicam o pacote 2 como pacote de verificação e relatam expected_carry_frames: 129 e expected_check_fails: true.
O repositório contém um WebM de controle de 43 ms e 4.185 bytes:
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
A entrada deve conter:
A_VORBIS;CodecDelay positivo (normalmente lido da faixa); eOpções úteis são --trigger-skip, --codec-delay e --sample-rate. --second-skip permanece como alias para a opção renomeada --trigger-skip. Os padrões são os valores necessários para o estado de falha conhecido. O comando imprime o relatório JSON em stdout e grava o mesmo relatório em --manifest quando essa opção é fornecida.
O candidato incluído no repositório tem 43 ms e 4.206 bytes. Seu SHA-256 é:
a0ab9e146c629f037b86612addc1ab6fff9200711d45aa5f5edbb9576cc206ac
Espera-se que o hash de saída mude se a entrada, a janela de pacotes ou as opções mudarem.
O caso M4A abusa de uma forma válida de tabela de amostras MP4, em vez do payload AAC codificado. O gerador altera uma semente da seguinte forma:
stsz por sample_size = 1.stsz, na primeira execução stsc e na primeira execução stts com o mesmo valor grande.stco para que o payload AAC de um byte ainda aponte para dentro de mdat.O demuxer fixado do FFmpeg trata a contagem declarada como autoritativa ao construir suas tabelas de índice e temporização. As estruturas relevantes usam aproximadamente 24 bytes por amostra declarada para AVIndexEntry e 12 bytes por amostra para dados de temporização: 36 bytes por entrada de contagem no total. A contagem máxima aceita na build testada é 178.956.969 (0x0AAAAAA9), o que projeta:
178,956,969 * 24 = 4,294,967,256 bytes
178,956,969 * 12 = 2,147,483,628 bytes
combined = 6,442,450,884 bytes
O renderer exato do Discord atingiu um pico de 6.614.761.472 bytes de memória privada ao carregar metadados. A contagem adjacente 178.956.970 é rejeitada pelo limite fixado do FFmpeg e permaneceu próxima da memória normal. Isso é consumo descontrolado de recursos (CWE-400), não um integer-wrap observado, alocação de tamanho negativo ou escrita fora dos limites. O payload AAC real pode ser truncado; a reprodução bem-sucedida não é necessária para atingir a grande alocação.
O gerador aceita intencionalmente uma semente em vez de embutir um codificador AAC. Use um arquivo AAC/M4A fast-start com uma tabela stsz explícita, uma execução stsc, uma ou mais entradas stts e offsets de chunk stco. Ele falha de forma segura se a estrutura necessária estiver ausente.
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
O --sample-count padrão é 178956969, o valor máximo aceito na build fixada. Use um valor menor para um teste de fumaça com pouca memória, por exemplo:
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 é um controle útil de rejeição adjacente, não o valor que dispara o gatilho. A flag opcional --moov-at-end move moov para depois de mdat e atualiza stco/co64; é útil ao testar um arquivo cujos bytes AAC físicos ocorrem antes de seus metadados, mas não é necessária para o mecanismo de alocação.
Para produzir esse layout explicitamente:
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
Os artefatos correspondentes são
samples/short-vorbis-dual-control.webm,
samples/short-vorbis-dual-crash.webm,
e samples/short-vorbis-dual-crash.json.