ref: 529bd01294c13a62f13fc4fe882eec175fc0bebc
parent: 0adf7303ecf08776205e85b5b65ee8bd3419945c
author: Timothy B. Terriberry <tterribe@xiph.org>
date: Tue May 23 11:48:40 EDT 2017
Fix uninitialized free in tag parsing. The tags have two possible representations of the binary suffix data. An implicit one: if comment_lengths and user_comments are NULL, then the length of the binary suffix data is 0 and the pointer to that data is also implicitly taken to be NULL. And an explicit one: if comment_lengths and user_comments are non-NULL, then comment_lengths[comments] and user_comments[comments] contains the length and pointer to the binary suffix data (where "comments" is the number of user comments). The implicit one allows us to initialize a tags struct without doing any allocations, which ensures it always succeeds. op_tags_ensure_capacity() may upgrade the implicit representation to the explicit representation. However, when doing so, it stores the implicit (0, NULL) values in their new explicit locations at the end of the array assuming the requested capacity will become the new size. If the caller of op_tags_ensure_capacity() then fails before enlarging the array to that size, then comment_lengths and user_comments will be non-NULL, but the explicit locations for the binary suffix data for the _old_ size may not have been initialized. In particular, in opus_tags_parse_impl(), if there was exactly one comment, and any of the three checks at the start of the main loop failed, then the explicit locations for the binary suffix data would remain uninitialized, and the call to opus_tags_clear() in the caller would attempt to free them. For counts larger than 1, the extra line marked "Needed by opus_tags_clear()" will update the explicit location as the array grows, but with a count of 1 this line never gets a chance to run. This patch fixes the problem by making op_tags_ensure_capacity() promote the implicit representation to explicit at _both_ the old and new array sizes. We could have fixed up opus_tags_parse_impl() to do this instead, as suggested in the original bug reports, but doing it this way ensures that the tags are always in a valid state when op_tags_ensure_capacity() returns. Thanks to the Google AutoFuzz Team for the report. Fixes #2324 Fixes #2325
--- a/src/info.c
+++ b/src/info.c
@@ -107,26 +107,33 @@
char **user_comments;
int *comment_lengths;
int cur_ncomments;
- char *binary_suffix_data;
- int binary_suffix_len;
size_t size;
if(OP_UNLIKELY(_ncomments>=(size_t)INT_MAX))return OP_EFAULT;
size=sizeof(*_tags->comment_lengths)*(_ncomments+1);
if(size/sizeof(*_tags->comment_lengths)!=_ncomments+1)return OP_EFAULT;
cur_ncomments=_tags->comments;
+ /*We only support growing.
+ Trimming requires cleaning up the allocated strings in the old space, and
+ is best handled separately if it's ever needed.*/
+ OP_ASSERT(_ncomments>=cur_ncomments);
comment_lengths=_tags->comment_lengths;
- binary_suffix_len=comment_lengths==NULL?0:comment_lengths[cur_ncomments];
comment_lengths=(int *)_ogg_realloc(_tags->comment_lengths,size);
if(OP_UNLIKELY(comment_lengths==NULL))return OP_EFAULT;
- comment_lengths[_ncomments]=binary_suffix_len;
+ if(_tags->comment_lengths==NULL){
+ OP_ASSERT(cur_ncomments==0);
+ comment_lengths[cur_ncomments]=0;
+ }
+ comment_lengths[_ncomments]=comment_lengths[cur_ncomments];
_tags->comment_lengths=comment_lengths;
size=sizeof(*_tags->user_comments)*(_ncomments+1);
if(size/sizeof(*_tags->user_comments)!=_ncomments+1)return OP_EFAULT;
- user_comments=_tags->user_comments;
- binary_suffix_data=user_comments==NULL?NULL:user_comments[cur_ncomments];
user_comments=(char **)_ogg_realloc(_tags->user_comments,size);
if(OP_UNLIKELY(user_comments==NULL))return OP_EFAULT;
- user_comments[_ncomments]=binary_suffix_data;
+ if(_tags->user_comments==NULL){
+ OP_ASSERT(cur_ncomments==0);
+ user_comments[cur_ncomments]=NULL;
+ }
+ user_comments[_ncomments]=user_comments[cur_ncomments];
_tags->user_comments=user_comments;
return 0;
}