
Análise técnica aprofundada do CVE-2021-25804, uma vulnerabilidade do analisador AVI do VLC. Inclui causa raiz, diff do patch e primitivas de exploração para pesquisadores de segurança.
Um arquivo AVI pode ter múltiplas faixas. Por exemplo, uma faixa de áudio e uma faixa de vídeo.
Para analisar um número arbitrário de faixas, o VLC primeiro deve determinar se uma determinada faixa está finalizada e reproduzível. Duas variáveis são configuradas com esse propósito. Ambas chamadas i_track.
A variável p_sys representa o arquivo avi para nossos propósitos. Assim, a variável i_track "oficial" que será usada para referenciar as faixas que são determinadas como finalizadas e reproduzíveis é p_sys->i_track. Ela é inicializada com uma macro aqui:
```
TAB_INIT(p_sys->i_track, p_sys->track);
//#define TAB_INIT( count, tab ) do {(count) = 0; (tab) = NULL; } while(0)
```
Enquanto isso, uma segunda variável i_track é configurada para contar TODAS as faixas, independentemente de estarem finalizadas/reproduzíveis ou não.
```
i_track = AVI_ChunkCount( p_hdrl, AVIFOURCC_strl, true );
```
A segunda i_track representa todos os chunks começando com 'strl'. Ela controla um loop 'for' que define a variável p_sys->i_track apenas se passar por uma série de verificações:
```
for( unsigned i = 0 ; i < i_track; i++ )
{
...
TAB_APPEND( p_sys->i_track, p_sys->track, tk );
//#define TAB_APPEND( count, tab, p ) {TAB_APPEND_CAST( , count, tab, p )}
//TAB_APPEND_CAST( ,p_sys->i_track, p_sys->track, tk)
//if( (count) > 0 ) (tab) = cast realloc( tab, sizeof( *(tab) ) * ( (count) + 1 ) );
//else (tab) = cast malloc( sizeof( *(tab) ) );
//if( !(tab) ) abort();
//(tab)[count] = (p);
//(count)++; //p_sys->i_track incremented here
}
```
Durante um loop 'for' subsequente, a variável p_sys->i_track é utilizada como o número do chunk em uma chamada para AVI_ChunkFind, que retorna o n-ésimo chunk que corresponde a um valor de chunk FOURCC, no nosso caso 'strl':
```
for( unsigned i = 0 ; i < p_sys->i_track; i++ )
{
avi_track_t *tk = p_sys->track[i];
if( tk->fmt.i_cat != AUDIO_ES ||
tk->idx.i_size < 1 ||
tk->i_scale != 1 ||
tk->i_samplesize != 0 )
continue;
//CVE-2021-25804
avi_chunk_list_t *p_strl = AVI_ChunkFind( p_hdrl, AVIFOURCC_strl, i, true );
//subsequently changed to: avi_chunk_list_t *p_strl = AVI_ChunkFind( p_hdrl, AVIFOURCC_strl, tk->fmt.i_id, true );
avi_chunk_strf_t *p_strf = AVI_ChunkFind( p_strl, AVIFOURCC_strf, 0, false );
if( !p_strf || p_strf->i_cat != AUDIO_ES )
continue;
const WAVEFORMATEX *p_wf = p_strf->u.p_wf;
```
Embora p_sys->i_track conte o número correto de faixas concluídas, ele não mantém registro de qual número de chunk foi definido. Isso significa que, se houver um chunk strl inválido antes do chunk strl válido, o primeiro chunk será corretamente ignorado ao configurar o objeto de faixa, mas quando ChunkFind é chamado, ele ainda selecionará o primeiro chunk inválido.
Após a criação do CVE-2021-25804, o problema foi corrigido alterando a variável de número do chunk do ChunkFind de i para tk->fmt.i_id. tk->fmt.i_id é definido logo acima da chamada TAB_APPEND() que vimos anteriormente:
```
tk->fmt.i_id = i;
if( p_strn && p_strn->p_str )
tk->fmt.psz_description = FromACP( p_strn->p_str );
//see es_format_truncated.c
tk->p_es = es_out_Add( p_demux->out, &tk->fmt );
TAB_APPEND( p_sys->i_track, p_sys->track, tk );
```
Isso parece corrigir o problema, pois tk->fmt.i_id é definido como i e, portanto, o chunk concluído deve ser o selecionado por ChunkFind(). No entanto, testes revelam que, na verdade, o valor de tk->fmt.i_id está definido como -1 no momento da chamada problemática para ChunkFind(). Este é o valor de inicialização definido durante uma chamada a es_format_Init().
```
void es_format_Init( es_format_t *fmt,
int i_cat, vlc_fourcc_t i_codec )
{
memset(fmt, 0, sizeof (*fmt));
fmt->i_cat = i_cat;
fmt->i_codec = i_codec;
fmt->i_profile = -1;
fmt->i_level = -1;
fmt->i_id = -1;
fmt->i_priority = ES_PRIORITY_SELECTABLE_MIN;
fmt->psz_language = NULL;
fmt->psz_description = NULL;
fmt->p_extra_languages = NULL;
if (fmt->i_cat == VIDEO_ES)
video_format_Init(&fmt->video, 0);
fmt->b_packetized = true;
fmt->p_extra = NULL;
}
```
Essa inicialização ocorre no mesmo loop 'for' em que fmt->i_id é definido como i.
```
for( unsigned i = 0 ; i < i_track; i++ )
{
...
switch( p_strh->i_type )
{
case( AVIFOURCC_auds ):
{
es_format_Init( &tk->fmt, AUDIO_ES, 0 );
...
}
...
}
tk->fmt.i_id = i;
if( p_strn && p_strn->p_str )
tk->fmt.psz_description = FromACP( p_strn->p_str );
tk->p_es = es_out_Add( p_demux->out, &tk->fmt );
TAB_APPEND( p_sys->i_track, p_sys->track, tk );
}
```