CVE-2009-3379: Mozilla Firefox Multiple Vulnerabilities
This CVE name which was released through MFSA-2009-63 is about a series of vulnerabilities mainly in the vorbis library code. I’m going to give a brief description of each one included so let’s start…
1) Vorbis Decoding Missing Checks
This one was reported by Lucas Adamski of Mozilla and the susceptible code resides in media/libfishsound/src/libfishsound/fishsound_vorbis.c, and specifically in the following routine.
#if FS_DECODE static long fs_vorbis_decode (FishSound * fsound, unsigned char * buf, long bytes) { FishSoundVorbisInfo * fsv = (FishSoundVorbisInfo *)fsound->codec_data; ogg_packet op; long samples; float * pcm_new; int ret; /* Make an ogg_packet structure to pass the data to libvorbis */ ... if (fsv->packetno < 3) { if ((ret = vorbis_synthesis_headerin (&fsv->vi, &fsv->vc, &op)) == 0) { if (fsv->vi.rate != 0) { debug_printf (1, "Got vorbis info: version %d\tchannels %d\trate %ld", fsv->vi.version, fsv->vi.channels, fsv->vi.rate); fsound->info.samplerate = fsv->vi.rate; fsound->info.channels = fsv->vi.channels; } } /* Decode comments from packet 1. Vorbis has 7 bytes of marker at the * start of vorbiscomment packet. */ if (fsv->packetno == 1 && bytes > 7 && buf[0] == 0x03 && !strncmp ((char *)&buf[1], "vorbis", 6)) { ... } else if (fsv->packetno == 2) { vorbis_synthesis_init (&fsv->vd, &fsv->vi); vorbis_block_init (&fsv->vd, &fsv->vb); } ... return 0; } #else /* !FS_DECODE */ #define fs_vorbis_decode NULL #endif
This function is used to decode an OGG file structure. In case of a packet number less than three, it will invoke vorbis_synthesis_headerin() to parse the vorbis information (represented through ‘&fsv->vi’) and the vorbis comments’ section (stored in ‘&fsv->vc’). The problem is that vorbis_synthesis_headerin() can fail in numerous situations as you can see below, the above code doesn’t check if it succeeds or fails. This can result in processing of an invalid vorbis structure.
/* The Vorbis header is in three packets; the initial small packet in the first page that identifies basic parameters, a second packet with bitstream comments and a third packet that holds the codebook. */ int vorbis_synthesis_headerin(vorbis_info *vi,vorbis_comment *vc,ogg_packet *op){ oggpack_buffer opb; ... if(memcmp(buffer,"vorbis",6)){ /* not a vorbis header */ return(OV_ENOTVORBIS); } switch(packtype){ case 0x01: /* least significant *bit* is read first */ if(!op->b_o_s){ /* Not the initial packet */ return(OV_EBADHEADER); } if(vi->rate!=0){ /* previously initialized info header */ return(OV_EBADHEADER); } ... case 0x03: /* least significant *bit* is read first */ if(vi->rate==0){ /* um... we didn't get the initial header */ return(OV_EBADHEADER); } ... case 0x05: /* least significant *bit* is read first */ if(vi->rate==0 || vc->vendor==NULL){ /* um... we didn;t get the initial header or comments yet */ return(OV_EBADHEADER); } ... default: /* Not a valid vorbis header type */ return(OV_EBADHEADER); break; } } } return(OV_EBADHEADER); }
This was patched by adding the missing checks like this:
fsound->info.channels = fsv->vi.channels; } + } else if (ret < 0) { + /* error occured while decoding the header */ + return -1; }
Since all of the error codes returned by vorbis_synthesis_headerin() are negative numbers, this check is sufficient and it will immediately return with no further processing.
A similar vulnerability was also present in fs_vorbis_decode() when it was handling the second packet of a three-or-less OGG structure. As you can read in the above snippet, it calls vorbis_synthesis_init() and vorbis_block_init() to initialize some members of the vorbis structure, however, those routines could fail and leave the structure in an inconsistent state for further processing. This was fixed by applying the following patch:
} else if (fsv->packetno == 2) { - vorbis_synthesis_init (&fsv->vd, &fsv->vi); - vorbis_block_init (&fsv->vd, &fsv->vb); + if (vorbis_synthesis_init (&fsv->vd, &fsv->vi) != 0) + return -1; + + if (vorbis_block_init (&fsv->vd, &fsv->vb) != 0) + return -1; } } else {
In addition, Lucas Adamski provided a PoC trigger OGG file that you download here.
2) Vorbis Codebook Integer Overflow
This next bug was reported to Mozilla by Reed Loden. However, for the original bug discovery (under CVE-2008-1423 name) Dan Kaminsky should be credited since he was the one who discover it.
The suscpetible code is available at the media/libvorbis/lib/vorbis_codebook.c which is the location of the libvorbis library. Here is a snippet of the vorbis_staticbook_unpack() routine…
/* unpacks a codebook from the packet buffer into the codebook struct, readies the codebook auxiliary structures for decode *************/ int vorbis_staticbook_unpack(oggpack_buffer *opb,static_codebook *s){ long i,j; memset(s,0,sizeof(*s)); s->allocedp=1; ... /* first the basic parameters */ s->dim=oggpack_read(opb,16); s->entries=oggpack_read(opb,24); ... int quantvals=0; switch(s->maptype){ case 1: quantvals=(s->dim==0?0:_book_maptype1_quantvals(s)); break; case 2: quantvals=s->entries*s->dim; break; } /* quantized values */ s->quantlist=_ogg_malloc(sizeof(*s->quantlist)*quantvals); for(i=0;i<quantvals;i++) s->quantlist[i]=oggpack_read(opb,s->q_quant); ... return(-1); }
As you can read from the function’s comments, this is used to unpack a codebook structure passed to it. The vulnerable code starts during the initialization of ‘s->dim’ (which represents the codebook dimensions) and ‘s->entries’ (which contains the codebook entries) using oggpack_read() function. This means that ‘s->dim’ can have values up to 2^16-1 (that results in 65535) and ‘s->entries’ up to 2^24-1 (which is 16777215). Next, it has a switch statement to check if there is a mapping to unpack, in case of ‘s->maptype’ set to two (aka. listed arbitrary values) it will perform a calculation that could result in an integer overflow, that is:
s->entries * s->dim
If someone uses the maximum available values, this will result in the following:
65535 * 16777215 = 1099494785025
which will definitely not fit in the 32-bit long, singed integer ‘quantvals’. The subsequent allocation using _ogg_malloc() will lead to allocation of insufficient space and the following operations on that area will almost certainly result in heap memory corruption.
To fix this bug, the following patch was used:
if(s->entries==-1)goto _eofout; + if(_ilog(s->dim)+_ilog(s->entries)>24)goto _eofout; + /* codeword ordering.... length ordered or unordered? */ switch((int)oggpack_read(opb,1)){
This is a simple check that the sum of the bits used in ‘s->dim’ and ‘s->entries’ aren’t more than 24, since _ilog() simply returns the number of bits as you can read at media/libvorbis/lib/vorbis_sharedbook.c…
/**** pack/unpack helpers ******************************************/ int _ilog(unsigned int v){ int ret=0; while(v){ ret++; v>>=1; } return(ret); }
3) libvorbis OGG Heap Buffer Overflows
This next vulnerability which was reported by Matthew Gregan can be found in media/libvorbis/lib/vorbis_res0.c in the function listed below.
/* vorbis_info is for range checking */ vorbis_info_residue *res0_unpack(vorbis_info *vi,oggpack_buffer *opb){ int j,acc=0; vorbis_info_residue0 *info=_ogg_calloc(1,sizeof(*info)); codec_setup_info *ci=vi->codec_setup; ... info->partitions=oggpack_read(opb,6)+1; info->groupbook=oggpack_read(opb,8); for(j=0;j<info->partitions;j++){ int cascade=oggpack_read(opb,3); if(oggpack_read(opb,1)) cascade|=(oggpack_read(opb,5)<<3); info->secondstages[j]=cascade; acc+=icount(cascade); } for(j=0;j<acc;j++) info->booklist[j]=oggpack_read(opb,8); ... if(info->groupbook>=ci->books)goto errout; for(j=0;j<acc;j++){ if(info->booklist[j]>=ci->books)goto errout; if(ci->book_param[info->booklist[j]]->maptype==0)goto errout; } ... int entries = ci->book_param[info->groupbook]->entries; int dim = ci->book_param[info->groupbook]->dim; int partvals = 1; while(dim>0){ partvals *= info->partitions; if(partvals > entries) goto errout; dim--; } info->partvals = partvals; } ... errout: res0_free_info(info); return(NULL); }
The problem with the above code is that the heap allocated ‘info->groupbook’ which should contain the huffbook for partitioning is initialized using oggpack_read() from the user controlled OGG file. The same applies for ‘info->partitions’ too (this represents the possible codebooks for a partition). The next ‘for’ loop will iterate for every possible codebook and and update ‘cascade’ and ‘info->secondstages’ accordingly. However, since the processed value is also retrieved directly from the user controlled buffer, it could be negative which will later result in memory corruption.
This first vulnerability was patched using the following approach:
info->groupbook=oggpack_read(opb,8); + /* check for premature EOP */ + if(info->groupbook<0)goto errout; + for(j=0;j<info->partitions;j++){ int cascade=oggpack_read(opb,3); - if(oggpack_read(opb,1)) - cascade|=(oggpack_read(opb,5)<<3); + int cflag=oggpack_read(opb,1); + if(cflag<0) goto errout; + if(cflag){ + int c=oggpack_read(opb,5); + if(c<0) goto errout; + cascade|=(c<<3); + } info->secondstages[j]=cascade;
First of all, before even beginning the processing, they check for negative values in ‘info->groupbook’ and the first ‘for’ loop was changed to check that the retrieved value (which is now temporarily stored in ‘cflag’) is neither negative nor zero and the value to be OR’d with ‘cascade’ is also checked for not being negative.
Back to res0_unpack(), the second ‘for’ loop used to initialize ‘info->booklist[]’ array directly from the OGG buffer was also patched in a similar manner…
acc+=icount(cascade); } - for(j=0;j<acc;j++) - info->booklist[j]=oggpack_read(opb,8); + for(j=0;j<acc;j++){ + int book=oggpack_read(opb,8); + if(book<0) goto errout; + info->booklist[j]=book; + } if(info->groupbook>=ci->books)goto errout;
Again, same negative values’ checks before proceeding with the update.
At last, during the last code of the above snippet of res0_unpack() you can read that integers ‘entries’ and ‘dim’ are initialized with the codebook entries and the codebook dimensions respectively. Then, a ‘while’ loop takes place. In it there is a multiplication that could result in an integer overflow since ‘partvals’ is just a 32-bit long signed integer. To avoid overflows the following patch was used.
if(partvals > entries) goto errout; dim--; } + if(partvals < entries) goto errout; }
So, before updating the ‘info->partvals’ (which should normally contain the “artitions ^ groupbook dimensions” as we can read at media/libvorbis/lib/backends.h) with the calculated ‘portvals’, it checks that the latter integer hasn’t overflowed to a negative value.
Matthew Gregan also gave a PoC OGG file to trigger the bug at will which you can find here.
4) libvorbis Missing Checks
This is the last bug under this CVE name which was reported by David Keeler of Stanford University. Here is the buggy code of vorbis_staticbook_unpack() as seen at media/libvorbis/lib/vorbis_codebook.c…
/* unpacks a codebook from the packet buffer into the codebook struct, readies the codebook auxiliary structures for decode *************/ int vorbis_staticbook_unpack(oggpack_buffer *opb,static_codebook *s){ long i,j; memset(s,0,sizeof(*s)); s->allocedp=1; ... /* codeword ordering.... length ordered or unordered? */ switch((int)oggpack_read(opb,1)){ case 0: /* unordered */ ... case 1: /* ordered */ { long length=oggpack_read(opb,5)+1; s->lengthlist=_ogg_malloc(sizeof(*s->lengthlist)*s->entries); for(i=0;i<s->entries;){ long num=oggpack_read(opb,_ilog(s->entries-i)); if(num==-1)goto _eofout; for(j=0;j<num && i<s->entries;j++,i++) s->lengthlist[i]=length; length++; } } break; default: /* EOF */ return(-1); } ... _errout: _eofout: vorbis_staticbook_clear(s); return(-1); }
The bug with this part of the routine is the case of an ordered codeword length. The long signed integer ‘length’ is initialized using user controlled OGG buffer and ‘s->lengthlist’ points to some heap allocated space that was obtained using _ogg_malloc() as you can read in the given code. The next part is a simple ‘for’ loop that iterates for each codebook entry and updates all of their ‘s->lengthlist[]’ array entries with the previously retrieved ‘length’. However, this value cannot be greater than 32 since it is used to contain the codeword length in bits!
Nevertheless, there is no check to ensure this, and an attacker could have complete control over this value. To fix this, the following patch was applied:
long num=oggpack_read(opb,_ilog(s->entries-i)); if(num==-1)goto _eofout; + if(length>32)goto _errout; for(j=0;j<num && i<s->entries;j++,i++)
Quite expected patch.
Additionally, David Keeler uploaded a PoC OGG file that can be used to trigger the vulnerability. This file can be downloaded here.
That’s all… I believe that those are a many bugs to be included under a single CVE name but they might know better. In any case, have fun!
Thanks!
This kind of post is great for case studies.
Appreciative
November 16, 2009 at 11:17