
تحليل فني متعمق لـ CVE-2021-25804، وهي ثغرة في محلل AVI الخاص بـ VLC. يتضمن السبب الجذري وفرق التصحيح والبدائيات الاستغلالية للباحثين الأمنيين.
يمكن أن يحتوي ملف 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 incremented here
}
```
خلال حلقة 'for' لاحقة، يُستخدم متغير p_sys->i_track كرقم القطعة في استدعاء AVI_ChunkFind، الذي يعيد القطعة رقم n المطابقة لقيمة FOURCC معينة، في حالتنا '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;
```
بينما يعدّ 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 );
//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 );
```
يبدو أن هذا يصلح المشكلة حيث يتم تعيين tk->fmt.i_id إلى i وبالتالي يجب أن تكون القطعة المكتملة هي التي يختارها ChunkFind(). ومع ذلك، يكشف الاختبار أن قيمة tk->fmt.i_id في الواقع تُضبط إلى -1 في وقت استدعاء ChunkFind() المخالف. هذه هي قيمة التهيئة التي يتم تعيينها أثناء استدعاء 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;
}
```
تحدث هذه التهيئة في نفس حلقة 'for' التي يتم فيها تعيين fmt->i_id إلى 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 );
}
```