[FFmpeg-devel] [PATCH] avformat/mov: fix overallocation when reading pssh/saiz

Marvin Scholz (ePirat) epirat07 at gmail.com
Mon Jun 12 15:06:52 EEST 2023


Hi,

> On 12. Jun 2023, at 13:56, Zhao Zhili <quinkblack at foxmail.com> wrote:
> 
> From: Zhao Zhili <zhilizhao at tencent.com>
> 
> mov_try_read_block() allocates 1MB at least, which can be more than
> enough. It was called when reading saiz box, which can appear
> periodically inside fmp4. This consumes a lot of memory.
> 
> We can fix mov_try_read_block() by clamp 'block_size' with 'size'.
> However, the function is harmful than helpful. It avoids allocating
> large memory when the real data is small. Even in that case, if
> allocating large memory directly failed, it's fine to return ENOMEM;
> if allocating success and reading doesn't match the given size, it's
> fine to free and return AVERROR_INVALIDDATA. In other cases, it's a
> waste of CPU and memory.
> 
> So I decided to remove the function, and replace it by call
> av_malloc() and avio_read() directly.
> 
> mov_read_saiz() and mov_read_pssh() need more check, but they don't
> belong to this patch.
> 
> Fixes #7641 and #9243.
> 
> Signed-off-by: Zhao Zhili <zhilizhao at tencent.com>
> ---
> libavformat/mov.c | 63 +++++++++++++++++++----------------------------
> 1 file changed, 25 insertions(+), 38 deletions(-)
> 
> diff --git a/libavformat/mov.c b/libavformat/mov.c
> index a8d004e02b..3d0969545a 100644
> --- a/libavformat/mov.c
> +++ b/libavformat/mov.c
> @@ -6649,38 +6649,6 @@ finish:
>     return ret;
> }
> 
> -/**
> - * Tries to read the given number of bytes from the stream and puts it in a
> - * newly allocated buffer.  This reads in small chunks to avoid allocating large
> - * memory if the file contains an invalid/malicious size value.

I fail to see how your replacement code addresses the malicious size value case that this function mitigated, see in more detail what I mean below…

> - */
> -static int mov_try_read_block(AVIOContext *pb, size_t size, uint8_t **data)
> -{
> -    const unsigned int block_size = 1024 * 1024;
> -    uint8_t *buffer = NULL;
> -    unsigned int alloc_size = 0, offset = 0;
> -    while (offset < size) {
> -        unsigned int new_size =
> -            alloc_size >= INT_MAX - block_size ? INT_MAX : alloc_size + block_size;
> -        uint8_t *new_buffer = av_fast_realloc(buffer, &alloc_size, new_size);
> -        unsigned int to_read = FFMIN(size, alloc_size) - offset;
> -        if (!new_buffer) {
> -            av_free(buffer);
> -            return AVERROR(ENOMEM);
> -        }
> -        buffer = new_buffer;
> -
> -        if (avio_read(pb, buffer + offset, to_read) != to_read) {
> -            av_free(buffer);
> -            return AVERROR_INVALIDDATA;
> -        }
> -        offset += to_read;
> -    }
> -
> -    *data = buffer;
> -    return 0;
> -}
> -
> static int mov_read_saiz(MOVContext *c, AVIOContext *pb, MOVAtom atom)
> {
>     MOVEncryptionIndex *encryption_index;
> @@ -6736,15 +6704,24 @@ static int mov_read_saiz(MOVContext *c, AVIOContext *pb, MOVAtom atom)
> 
>     encryption_index->auxiliary_info_default_size = avio_r8(pb);
>     sample_count = avio_rb32(pb);
> -    encryption_index->auxiliary_info_sample_count = sample_count;
> 
>     if (encryption_index->auxiliary_info_default_size == 0) {
> -        ret = mov_try_read_block(pb, sample_count, &encryption_index->auxiliary_info_sizes);
> -        if (ret < 0) {
> -            av_log(c->fc, AV_LOG_ERROR, "Failed to read the auxiliary info\n");
> +        encryption_index->auxiliary_info_sizes = av_malloc(sample_count);
> +        if (!encryption_index->auxiliary_info_sizes)
> +            return AVERROR(ENOMEM);
> +
> +        ret = avio_read(pb, encryption_index->auxiliary_info_sizes, sample_count);
> +        if (ret != sample_count) {
> +            av_freep(&encryption_index->auxiliary_info_sizes);
> +
> +            if (ret >= 0)
> +                ret = AVERROR_INVALIDDATA;
> +            av_log(c->fc, AV_LOG_ERROR, "Failed to read the auxiliary info, %s\n",
> +                   av_err2str(ret));
>             return ret;
>         }
>     }
> +    encryption_index->auxiliary_info_sample_count = sample_count;
> 
>     if (encryption_index->auxiliary_offsets_count) {
>         return mov_parse_auxiliary_info(c, sc, pb, encryption_index);
> @@ -6913,9 +6890,19 @@ static int mov_read_pssh(MOVContext *c, AVIOContext *pb, MOVAtom atom)
>     }
> 
>     extra_data_size = avio_rb32(pb);
> -    ret = mov_try_read_block(pb, extra_data_size, &extra_data);
> -    if (ret < 0)
> +    extra_data = av_malloc(extra_data_size);

If I understand correctly you are now effectively passing a potentially malicious size value directly to malloc, allowing an attacker to exhaust memory with a crafted file.

> +    if (!extra_data) {
> +        ret = AVERROR(ENOMEM);
>         goto finish;
> +    }
> +    ret = avio_read(pb, extra_data, extra_data_size);
> +    if (ret != extra_data_size) {
> +        av_free(extra_data);
> +
> +        if (ret >= 0)
> +            ret = AVERROR_INVALIDDATA;
> +        goto finish;
> +    }
> 
>     av_freep(&info->data);  // malloc(0) may still allocate something.
>     info->data = extra_data;
> -- 
> 2.25.1
> 
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel at ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
> 
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request at ffmpeg.org with subject "unsubscribe".


More information about the ffmpeg-devel mailing list