Open() { ... //only check that ever happens for whether or not an index exists prior to AVI_IndexLoad_indx() i_do_index = var_InheritInteger( p_demux, "avi-index" ); if( i_do_index == 1 ) /* Always fix */ { aviindex: if( p_sys->b_fastseekable ) //off by default in 3.0.15 { AVI_IndexCreate( p_demux ); } else if( p_sys->b_seekable ) { AVI_IndexLoad( p_demux ); //default case } else { msg_Warn( p_demux, "cannot create index (unseekable stream)" ); } } else if( p_sys->b_seekable ) { AVI_IndexLoad( p_demux ); } ... } static void AVI_IndexLoad( demux_t *p_demux ) { demux_sys_t *p_sys = p_demux->p_sys; /* Load indexes */ assert( p_sys->i_track <= 100 ); avi_index_t p_idx_indx[p_sys->i_track]; avi_index_t p_idx_idx1[p_sys->i_track]; for( unsigned i = 0; i < p_sys->i_track; i++ ) { avi_index_Init( &p_idx_indx[i] ); avi_index_Init( &p_idx_idx1[i] ); } uint64_t i_indx_last_pos = p_sys->i_movi_lastchunk_pos; uint64_t i_idx1_last_pos = p_sys->i_movi_lastchunk_pos; AVI_IndexLoad_indx( p_demux, p_idx_indx, &i_indx_last_pos ); if( !p_sys->b_odml ) AVI_IndexLoad_idx1( p_demux, p_idx_idx1, &i_idx1_last_pos ); ... } //zz open() -> AVI_IndexLoad() -> AVI_IndexLoad_indx() static void AVI_IndexLoad_indx( demux_t *p_demux, avi_index_t p_index[], uint64_t *pi_last_offset ) { //i_indx_last_pos = p_sys->i_movi_lastchunk_pos; demux_sys_t *p_sys = p_demux->p_sys; avi_chunk_list_t *p_riff; avi_chunk_list_t *p_hdrl; p_riff = AVI_ChunkFind( &p_sys->ck_root, AVIFOURCC_RIFF, 0, true); p_hdrl = AVI_ChunkFind( p_riff, AVIFOURCC_hdrl, 0, true ); for( unsigned i_stream = 0; i_stream < p_sys->i_track; i_stream++ ) { avi_chunk_list_t *p_strl; avi_chunk_indx_t *p_indx; #define p_stream p_sys->track[i_stream] p_strl = AVI_ChunkFind( p_hdrl, AVIFOURCC_strl, i_stream, true ); //indx fourcc used in the Super Index Chunk , https://web.archive.org/web/20191226055430/http://www.morgan-multimedia.com/download/odmlff2.pdf - page 16 p_indx = AVI_ChunkFind( p_strl, AVIFOURCC_indx, 0, false ); if( !p_indx ) { if( p_sys->b_odml ) msg_Warn( p_demux, "cannot find indx (misdetect/broken OpenDML file?)" ); continue; } if( p_indx->i_indextype == AVI_INDEX_OF_CHUNKS ) { __Parse_indx( p_demux, &p_index[i_stream], pi_last_offset, p_indx ); } else if( p_indx->i_indextype == AVI_INDEX_OF_INDEXES ) //this is the expected value for super index { if ( !p_sys->b_seekable ) return; //if seekable & INDEX_of_INDEXES avi_chunk_t ck_sub; for( unsigned i = 0; i < p_indx->i_entriesinuse; i++ ) { if( vlc_stream_Seek( p_demux->s, p_indx->idx.super[i].i_offset ) || //as long as the chunk isnt null and the fourcc isnt 0 & there are at least 7 bytes left in the file ChunkRead will return a value AVI_ChunkRead( p_demux->s, &ck_sub, NULL ) ) { break; } //CVE-2021-25801 //super index points to an offset with the 13th byte set to 0x01 //but no check is done on whether or not its actually pointing to a valid indx field chunk if( ck_sub.indx.i_indextype == AVI_INDEX_OF_CHUNKS ) __Parse_indx( p_demux, &p_index[i_stream], pi_last_offset, &ck_sub.indx ); AVI_ChunkClean( p_demux->s, &ck_sub ); } /*//CVE-2021-25801 subsequently changed to: if( ck_sub.common.i_chunk_fourcc == AVIFOURCC_indx && ck_sub.indx.i_indextype == AVI_INDEX_OF_CHUNKS ) __Parse_indx( p_demux, &p_index[i_stream], pi_last_offset, &ck_sub.indx );*/ } else { msg_Warn( p_demux, "unknown type index(0x%x)", p_indx->i_indextype ); } #undef p_stream } } //AVI_IndexLoad_indx() -> __Parse_indx() static void __Parse_indx( demux_t *p_demux, avi_index_t *p_index, uint64_t *pi_max_offset, avi_chunk_indx_t *p_indx ) { //i_indx_last_pos = p_sys->i_movi_lastchunk_pos //p_indx = &ck_sub.indx //p_index = &p_index[istream] avi_entry_t index; p_demux->p_sys->b_indexloaded = true; msg_Dbg( p_demux, "loading subindex(0x%x) %d entries", p_indx->i_indextype, p_indx->i_entriesinuse ); if( p_indx->i_indexsubtype == 0 ) { //overflow by setting i_entriesinuse to arbitrary high value resulting in out of bounds read? for( unsigned i = 0; i < p_indx->i_entriesinuse; i++ ) { //indxFieldChunk.dwChunkId index.i_id = p_indx->i_id; //?indxFieldChunk.dwSize[0:0] index.i_flags = p_indx->idx.std[i].i_size & 0x80000000 ? 0 : AVIIF_KEYFRAME; //only keep the most significant bit //indxFieldChunk.qwBaseOffset + indxFieldChunk.dwOffset - 8 index.i_pos = p_indx->i_baseoffset + p_indx->idx.std[i].i_offset - 8; //0x00 - 0xfffffff7 //?indxFieldChunk.dwSize[1:7] index.i_length = p_indx->idx.std[i].i_size&0x7fffffff; //set most significant bit to 0 index.i_lengthtotal = index.i_length; avi_index_Append( p_index, pi_max_offset, &index ); } } else if( p_indx->i_indexsubtype == AVI_INDEX_2FIELD ) { for( unsigned i = 0; i < p_indx->i_entriesinuse; i++ ) { index.i_id = p_indx->i_id; index.i_flags = p_indx->idx.field[i].i_size & 0x80000000 ? 0 : AVIIF_KEYFRAME; index.i_pos = p_indx->i_baseoffset + p_indx->idx.field[i].i_offset - 8; //<-Access Violation occurs here? index.i_length = p_indx->idx.field[i].i_size; index.i_lengthtotal = index.i_length; avi_index_Append( p_index, pi_max_offset, &index ); } } else { msg_Warn( p_demux, "unknown subtype index(0x%x)", p_indx->i_indexsubtype ); } } void AVI_ChunkClean( stream_t *s, avi_chunk_t *p_chk ) { int i_index; avi_chunk_t *p_child, *p_next; if( !p_chk ) { return; } /* Free all child chunk */ p_child = p_chk->common.p_first; while( p_child ) { p_next = p_child->common.p_next; AVI_ChunkClean( s, p_child ); free( p_child ); p_child = p_next; } i_index = AVI_ChunkFunctionFind( p_chk->common.i_chunk_fourcc ); if( AVI_Chunk_Function[i_index].AVI_ChunkFree_function ) { #ifdef AVI_DEBUG msg_Dbg( (vlc_object_t*)s, "free chunk %4.4s", (char*)&p_chk->common.i_chunk_fourcc ); #endif AVI_Chunk_Function[i_index].AVI_ChunkFree_function( p_chk); } else if( p_chk->common.i_chunk_fourcc != 0 ) { msg_Warn( (vlc_object_t*)s, "unknown chunk: %4.4s (not unloaded)", (char*)&p_chk->common.i_chunk_fourcc ); } p_chk->common.p_first = NULL; return; } //__Parse_index() -> avi_index_Append() static void avi_index_Append( avi_index_t *p_index, uint64_t *pi_last_pos, avi_entry_t *p_entry ) { //p_entry = ck_sub.indx.p_indx[] i_indx_last_pos = p_sys->i_movi_lastchunk_pos /* Update last chunk position */ if( *pi_last_pos < p_entry->i_pos ) *pi_last_pos = p_entry->i_pos; //i_movi_lastchunk_pos - 0xfffffff7 /* add the entry */ if( p_index->i_size >= p_index->i_max ) { p_index->i_max += 16384; p_index->p_entry = realloc_or_free( p_index->p_entry, p_index->i_max * sizeof( *p_index->p_entry ) ); /*vlc_arrays.h static inline void *realloc_or_free( void *p, size_t sz ) { void *n = realloc(p,sz); if( !n ) free(p); return n; } */ if( !p_index->p_entry ) return; } /* calculate cumulate length */ if( p_index->i_size > 0 ) { p_entry->i_lengthtotal = p_index->p_entry[p_index->i_size - 1].i_length + p_index->p_entry[p_index->i_size - 1].i_lengthtotal; } else { p_entry->i_lengthtotal = 0; } //is this the buffer overflow? //if i_size > sizeof( p_index->p_entry) == BO? //TODO do we control i_size? p_index->p_entry[p_index->i_size++] = *p_entry; } //__Parse_indx() -> AVI_ChunkRead() && AVI_IndexLoad_indx() int AVI_ChunkRead( stream_t *s, avi_chunk_t *p_chk, avi_chunk_t *p_father ) { int i_index; if( !p_chk ) { msg_Warn( (vlc_object_t*)s, "cannot read null chunk" ); return VLC_EGENERIC; } if( AVI_ChunkReadCommon( s, p_chk, p_father ) ) return VLC_EGENERIC; if( p_chk->common.i_chunk_fourcc == VLC_FOURCC( 0, 0, 0, 0 ) ) { msg_Warn( (vlc_object_t*)s, "found null fourcc chunk (corrupted file?)" ); return AVI_ZERO_FOURCC; } p_chk->common.p_father = p_father; i_index = AVI_ChunkFunctionFind( p_chk->common.i_chunk_fourcc ); if( AVI_Chunk_Function[i_index].AVI_ChunkRead_function ) { return AVI_Chunk_Function[i_index].AVI_ChunkRead_function( s, p_chk ); } else if( ( ((char*)&p_chk->common.i_chunk_fourcc)[0] == 'i' && ((char*)&p_chk->common.i_chunk_fourcc)[1] == 'x' ) || ( ((char*)&p_chk->common.i_chunk_fourcc)[2] == 'i' && ((char*)&p_chk->common.i_chunk_fourcc)[3] == 'x' ) ) { p_chk->common.i_chunk_fourcc = AVIFOURCC_indx; return AVI_ChunkRead_indx( s, p_chk ); } msg_Warn( (vlc_object_t*)s, "unknown chunk: %4.4s (not loaded)", (char*)&p_chk->common.i_chunk_fourcc ); return AVI_NextChunk( s, p_chk ); } //libavi.c static int AVI_NextChunk( stream_t *s, avi_chunk_t *p_chk ) { avi_chunk_t chk; if( !p_chk ) { if( AVI_ChunkReadCommon( s, &chk, NULL ) ) { return VLC_EGENERIC; } p_chk = &chk; } return AVI_GotoNextChunk( s, p_chk ); } //AVI_ChunkRead() -> AVI_ChunkReadCommon() static int AVI_ChunkReadCommon( stream_t *s, avi_chunk_t *p_chk, const avi_chunk_t *p_father ) { const uint8_t *p_peek; memset( p_chk, 0, sizeof( avi_chunk_t ) ); const uint64_t i_pos = vlc_stream_Tell( s ); if( vlc_stream_Peek( s, &p_peek, 8 ) < 8 ) { if( stream_Size( s ) > 0 && (uint64_t) stream_Size( s ) > i_pos ) msg_Warn( s, "can't peek at %"PRIu64, i_pos ); else //creating an indx that points to an absolute offset outside of the file results in this error thrown msg_Dbg( s, "no more data at %"PRIu64, i_pos ); return VLC_EGENERIC; } p_chk->common.i_chunk_fourcc = GetFOURCC( p_peek ); p_chk->common.i_chunk_size = GetDWLE( p_peek + 4 ); p_chk->common.i_chunk_pos = i_pos; if( p_chk->common.i_chunk_size >= UINT64_MAX - 8 || p_chk->common.i_chunk_pos > UINT64_MAX - 8 || UINT64_MAX - p_chk->common.i_chunk_pos - 8 < __EVEN(p_chk->common.i_chunk_size) ) return VLC_EGENERIC; if( p_father && AVI_ChunkEnd( p_chk ) > AVI_ChunkEnd( p_father ) ) { msg_Warn( s, "chunk %4.4s does not fit into parent %"PRIu64, (char*)&p_chk->common.i_chunk_fourcc, AVI_ChunkEnd( p_father ) ); /* How hard is to produce files with the correct declared size ? */ if( p_father->common.i_chunk_fourcc != AVIFOURCC_RIFF || p_father->common.p_father == NULL || p_father->common.p_father->common.p_father != NULL ) /* Root > RIFF only */ return VLC_EGENERIC; } #ifdef AVI_DEBUG msg_Dbg( (vlc_object_t*)s, "found chunk, fourcc: %4.4s size:%"PRIu64" pos:%"PRIu64, (char*)&p_chk->common.i_chunk_fourcc, p_chk->common.i_chunk_size, p_chk->common.i_chunk_pos ); #endif return VLC_SUCCESS; } //AVI_ChunkReadCommon() -> vlc_stream_Peek( s, &p_peek, 8 ) //src/input/stream.c ssize_t vlc_stream_Peek(stream_t *s, const uint8_t **restrict bufp, size_t len) { stream_priv_t *priv = (stream_priv_t *)s; block_t *peek; peek = priv->peek; if (peek == NULL) { peek = priv->block; priv->peek = peek; priv->block = NULL; } //hit if (peek == NULL) { peek = block_Alloc(len); if (unlikely(peek == NULL)) return VLC_ENOMEM; peek->i_buffer = 0; } else if (peek->i_buffer < len) { size_t avail = peek->i_buffer; peek = block_TryRealloc(peek, 0, len); if (unlikely(peek == NULL)) return VLC_ENOMEM; peek->i_buffer = avail; } priv->peek = peek; *bufp = peek->p_buffer; while (peek->i_buffer < len) { size_t avail = peek->i_buffer; ssize_t ret; ret = vlc_stream_ReadRaw(s, peek->p_buffer + avail, len - avail); if (ret < 0) continue; peek->i_buffer += ret; if (ret == 0) return peek->i_buffer; } return len; } //libavi.c // AVI_IndexLoad_indx() -> AVI_ChunkFind() void *AVI_ChunkFind_( avi_chunk_t *p_chk, vlc_fourcc_t i_fourcc, int i_number, bool b_list ) { if( !p_chk ) return NULL; for( avi_chunk_t *p_child = p_chk->common.p_first; p_child; p_child = p_child->common.p_next ) { if( b_list && p_child->list.i_type == 0 ) continue; if( p_child->common.i_chunk_fourcc != i_fourcc && (!b_list || p_child->list.i_type != i_fourcc) ) continue; if( i_number-- == 0 ) return p_child; /* We found it */ } return NULL; } static int AVI_ChunkRead_indx( stream_t *s, avi_chunk_t *p_chk ) { unsigned int i_count, i; int i_ret = VLC_SUCCESS; int32_t i_dummy; VLC_UNUSED(i_dummy); avi_chunk_indx_t *p_indx = (avi_chunk_indx_t*)p_chk; AVI_READCHUNK_ENTER; AVI_READ2BYTES( p_indx->i_longsperentry ); AVI_READ1BYTE ( p_indx->i_indexsubtype ); AVI_READ1BYTE ( p_indx->i_indextype ); AVI_READ4BYTES( p_indx->i_entriesinuse ); AVI_READ4BYTES( p_indx->i_id ); p_indx->idx.std = NULL; p_indx->idx.field = NULL; p_indx->idx.super = NULL; if( p_indx->i_indextype == AVI_INDEX_OF_CHUNKS && p_indx->i_indexsubtype == 0 ) { AVI_READ8BYTES( p_indx->i_baseoffset ); AVI_READ4BYTES( i_dummy ); /* #define AVI_READCHUNK_ENTER \ int64_t i_read = __EVEN(p_chk->common.i_chunk_size ) + 8; \ if( i_read > 100000000 ) \ { \ msg_Err( s, "Big chunk ignored" ); \ return VLC_EGENERIC; \ } \ */ i_count = __MIN( p_indx->i_entriesinuse, i_read / 8 ); p_indx->i_entriesinuse = i_count; p_indx->idx.std = calloc( i_count, sizeof( indx_std_entry_t ) ); if( i_count == 0 || p_indx->idx.std ) { for( i = 0; i < i_count; i++ ) { AVI_READ4BYTES( p_indx->idx.std[i].i_offset ); AVI_READ4BYTES( p_indx->idx.std[i].i_size ); } } else i_ret = VLC_EGENERIC; } else if( p_indx->i_indextype == AVI_INDEX_OF_CHUNKS && p_indx->i_indexsubtype == AVI_INDEX_2FIELD ) { AVI_READ8BYTES( p_indx->i_baseoffset ); AVI_READ4BYTES( i_dummy ); i_count = __MIN( p_indx->i_entriesinuse, i_read / 12 ); p_indx->i_entriesinuse = i_count; p_indx->idx.field = calloc( i_count, sizeof( indx_field_entry_t ) ); if( i_count == 0 || p_indx->idx.field ) { for( i = 0; i < i_count; i++ ) { AVI_READ4BYTES( p_indx->idx.field[i].i_offset ); AVI_READ4BYTES( p_indx->idx.field[i].i_size ); AVI_READ4BYTES( p_indx->idx.field[i].i_offsetfield2 ); } } else i_ret = VLC_EGENERIC; } else if( p_indx->i_indextype == AVI_INDEX_OF_INDEXES ) { p_indx->i_baseoffset = 0; AVI_READ4BYTES( i_dummy ); AVI_READ4BYTES( i_dummy ); AVI_READ4BYTES( i_dummy ); i_count = __MIN( p_indx->i_entriesinuse, i_read / 16 ); p_indx->i_entriesinuse = i_count; p_indx->idx.super = calloc( i_count, sizeof( indx_super_entry_t ) ); if( i_count == 0 || p_indx->idx.super ) { for( i = 0; i < i_count; i++ ) { AVI_READ8BYTES( p_indx->idx.super[i].i_offset ); AVI_READ4BYTES( p_indx->idx.super[i].i_size ); AVI_READ4BYTES( p_indx->idx.super[i].i_duration ); } } else i_ret = VLC_EGENERIC; } else { msg_Warn( (vlc_object_t*)s, "unknown type/subtype index" ); } #ifdef AVI_DEBUG msg_Dbg( (vlc_object_t*)s, "indx: type=%d subtype=%d entry=%d", p_indx->i_indextype, p_indx->i_indexsubtype, p_indx->i_entriesinuse ); #endif AVI_READCHUNK_EXIT( i_ret ); } ///src/input/stream.c int vlc_stream_Seek(stream_t *s, uint64_t offset) { stream_priv_t *priv = (stream_priv_t *)s; priv->eof = false; block_t *peek = priv->peek; if (peek != NULL) { if (offset >= priv->offset && offset <= (priv->offset + peek->i_buffer)) { /* Seeking within the peek buffer */ size_t fwd = offset - priv->offset; peek->p_buffer += fwd; peek->i_buffer -= fwd; priv->offset = offset; if (peek->i_buffer == 0) { priv->peek = NULL; block_Release(peek); } return VLC_SUCCESS; } } else { if (priv->offset == offset) return VLC_SUCCESS; /* Nothing to do! */ } if (s->pf_seek == NULL) return VLC_EGENERIC; int ret = s->pf_seek(s, offset); if (ret != VLC_SUCCESS) return ret; priv->offset = offset; if (peek != NULL) { priv->peek = NULL; block_Release(peek); } if (priv->block != NULL) { block_Release(priv->block); priv->block = NULL; } return VLC_SUCCESS; } //libavi.c static int AVI_GotoNextChunk( stream_t *s, const avi_chunk_t *p_chk ) { bool b_seekable = false; const uint64_t i_offset = AVI_ChunkEnd( p_chk ); if ( !vlc_stream_Control(s, STREAM_CAN_SEEK, &b_seekable) && b_seekable ) { return vlc_stream_Seek( s, i_offset ); } else { ssize_t i_read = i_offset - vlc_stream_Tell( s ); return (i_read >=0 && vlc_stream_Read( s, NULL, i_read ) == i_read) ? VLC_SUCCESS : VLC_EGENERIC; } } //libavi.c static uint64_t AVI_ChunkEnd( const avi_chunk_t *p_ck ) { return p_ck->common.i_chunk_pos + AVI_ChunkSize( p_ck ); }