一个AVI文件可以有多个轨道。例如,一个音频轨道和一个视频轨道。
为了解析任意数量的轨道,VLC必须首先确定一个给定的轨道是否已完成并可播放。为此设置有两个变量,都命名为 i_track。
在我们的上下文中,变量 p_sys 代表AVI文件。因此,“官方”的 i_track 变量(用于引用那些被确定为已完成并可播放的轨道)是 p_sys->i_track。它在此处通过一个宏进行初始化:
```
TAB_INIT(p_sys->i_track, p_sys->track);
//#define TAB_INIT( count, tab ) do {(count) = 0; (tab) = NULL; } while(0)
```
同时,设置了第二个 i_track 变量用于计数所有轨道,无论它们是否已完成/可播放:
```
i_track = AVI_ChunkCount( p_hdrl, AVIFOURCC_strl, true );
```
第二个 i_track 代表所有以 'strl' 开头的块。它控制着一个 'for' 循环,该循环仅在通过一系列检查后才设置 p_sys->i_track 变量:
```
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 在此处递增
}
```
在随后的一个 'for' 循环中,p_sys->i_track 变量被用作 AVI_ChunkFind 调用的块编号,该函数返回匹配给定 FOURCC 块值的第 n 个块,本例中为 '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 );
//随后更改为: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;
```
尽管 p_sys->i_track 确实计数了正确数量的已完成轨道,但它没有记录这些轨道对应的块编号。这意味着如果在一个有效的 strl 块之前有一个无效的 strl 块,那么第一个块在设置轨道对象时会被正确忽略,但当调用 ChunkFind 时,它仍然会选择第一个无效的块。
在 CVE-2021-25804 被创建之后,该问题通过将 ChunkFind 的块编号变量从 i 改为 tk->fmt.i_id 进行了修补。tk->fmt.i_id 是在我们之前看到的 TAB_APPEND() 调用正上方首次设置的:
```
tk->fmt.i_id = i;
if( p_strn && p_strn->p_str )
tk->fmt.psz_description = FromACP( p_strn->p_str );
//参见 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 );
```
这似乎修复了问题,因为 tk->fmt.i_id 被设置为 i,因此完成的块应该是 ChunkFind() 所选择的块。然而,测试显示,实际上在调用有问题的 ChunkFind() 时,tk->fmt.i_id 的值被设置为 -1。这是在对 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;
}
```
此初始化发生在与 fmt->i_id 被设置为 i 相同的 'for' 循环中。
```
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 );
}
```