[FFmpeg-devel] [PATCH] avformat/mux: Fix double-free when using AVPacket.opaque_ref

Lynne dev at lynne.ee
Fri Sep 3 19:49:00 EEST 2021


3 Sept 2021, 17:23 by andreas.rheinhardt at outlook.com:

> Andreas Rheinhardt:
>
>> Up until now, ff_write_chained() copied the packet (manually, not with
>> av_packet_move_ref()) from a packet given to it to a stack packet whose
>> timing and stream_index is then modified before being sent to another
>> muxer via av_(interleaved_)write_frame(). Afterwards it is intended to
>> sync the fields of the packet relevant to freeing again; yet this only
>> encompasses buf, side_data and side_data_elems and not the newly added
>> opaque_ref. The other fields are not synced so that the returned packet
>> can have a size > 0 and data != NULL despite its buf being NULL (this
>> always happens in the interleaved codepath; before commit
>> fe251f77c80b0512ab8907902e1dbed3f4fe1aad it could also happen in the
>> noninterleaved one). This leads to double-frees if the interleaved
>> codepath is used and opaque_ref is set.
>>
>> This commit therefore changes this by directly reusing the packet
>> instead of a spare packet. Given that av_write_frame() does not
>> change the packet given to it, one only needs to restore the timing
>> information to return it as it was; for the interleaved codepath
>> it is not possible to do likewise*, because av_interleaved_write_frame()
>> takes ownership of the packets given to it and returns blank packets.
>> But precisely because of this users of the interleaved codepath
>> have no legitimate expectation that their packet will be returned
>> unchanged. In line with av_interleaved_write_frame() ff_write_chained()
>> therefore returns blank packets when using the interleaved codepath.
>>
>> Making the only user of said codepath compatible with this was trivial.
>>
>> *: Unless one wanted to create a full new reference.
>>
>> Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt at outlook.com>
>> ---
>> ff_write_chained() will be redundant when we allow muxers to convert
>> timestamps internally (we can then make the slave muxer do the
>> conversion). So I am not really worried about the case of adding
>> a new timestamp-related field and forgetting to reset it in
>> ff_write_chained().
>>
>>  libavformat/internal.h |  3 ++-
>>  libavformat/mux.c      | 35 ++++++++++++++++++++++-------------
>>  libavformat/segment.c  |  4 +++-
>>  3 files changed, 27 insertions(+), 15 deletions(-)
>>
>> diff --git a/libavformat/internal.h b/libavformat/internal.h
>> index 4fc1154b9d..9d7312c0e2 100644
>> --- a/libavformat/internal.h
>> +++ b/libavformat/internal.h
>> @@ -506,7 +506,8 @@ void ff_sdp_write_media(char *buff, int size, AVStream *st, int idx,
>>  *
>>  * @param dst the muxer to write the packet to
>>  * @param dst_stream the stream index within dst to write the packet to
>> - * @param pkt the packet to be written
>> + * @param pkt the packet to be written. It will be returned blank when
>> + *            av_interleaved_write_frame() is used, unchanged otherwise.
>>  * @param src the muxer the packet originally was intended for
>>  * @param interleave 0->use av_write_frame, 1->av_interleaved_write_frame
>>  * @return the value av_write_frame returned
>> diff --git a/libavformat/mux.c b/libavformat/mux.c
>> index b1ad0dd561..6ba1306f2b 100644
>> --- a/libavformat/mux.c
>> +++ b/libavformat/mux.c
>> @@ -643,12 +643,12 @@ static void guess_pkt_duration(AVFormatContext *s, AVStream *st, AVPacket *pkt)
>>  */
>>  static int write_packet(AVFormatContext *s, AVPacket *pkt)
>>  {
>> +    AVStream *const st = s->streams[pkt->stream_index];
>>  int ret;
>>  
>>  // If the timestamp offsetting below is adjusted, adjust
>>  // ff_interleaved_peek similarly.
>>  if (s->output_ts_offset) {
>> -        AVStream *st = s->streams[pkt->stream_index];
>>  int64_t offset = av_rescale_q(s->output_ts_offset, AV_TIME_BASE_Q, st->time_base);
>>  
>>  if (pkt->dts != AV_NOPTS_VALUE)
>> @@ -658,7 +658,6 @@ static int write_packet(AVFormatContext *s, AVPacket *pkt)
>>  }
>>  
>>  if (s->avoid_negative_ts > 0) {
>> -        AVStream *st = s->streams[pkt->stream_index];
>>  int64_t offset = st->internal->mux_ts_offset;
>>  int64_t ts = s->internal->avoid_negative_ts_use_pts ? pkt->pts : pkt->dts;
>>  
>> @@ -719,7 +718,7 @@ static int write_packet(AVFormatContext *s, AVPacket *pkt)
>>  }
>>  
>>  if (ret >= 0)
>> -        s->streams[pkt->stream_index]->nb_frames++;
>> +        st->nb_frames++;
>>  
>>  return ret;
>>  }
>> @@ -1192,6 +1191,7 @@ int av_write_frame(AVFormatContext *s, AVPacket *in)
>>  pkt = in;
>>  } else {
>>  /* We don't own in, so we have to make sure not to modify it.
>> +         * (ff_write_chained() relies on this fact.)
>>  * The following avoids copying in's data unnecessarily.
>>  * Copying side data is unavoidable as a bitstream filter
>>  * may change it, e.g. free it on errors. */
>> @@ -1291,21 +1291,30 @@ int av_get_output_timestamp(struct AVFormatContext *s, int stream,
>>  int ff_write_chained(AVFormatContext *dst, int dst_stream, AVPacket *pkt,
>>  AVFormatContext *src, int interleave)
>>  {
>> -    AVPacket local_pkt;
>> +    int64_t pts = pkt->pts, dts = pkt->dts, duration = pkt->duration;
>> +    int stream_index = pkt->stream_index;
>> +    AVRational time_base = pkt->time_base;
>>  int ret;
>>  
>> -    local_pkt = *pkt;
>> -    local_pkt.stream_index = dst_stream;
>> +    pkt->stream_index = dst_stream;
>>  
>> -    av_packet_rescale_ts(&local_pkt,
>> -                         src->streams[pkt->stream_index]->time_base,
>> +    av_packet_rescale_ts(pkt,
>> +                         src->streams[stream_index]->time_base,
>>  dst->streams[dst_stream]->time_base);
>>  
>> -    if (interleave) ret = av_interleaved_write_frame(dst, &local_pkt);
>> -    else            ret = av_write_frame(dst, &local_pkt);
>> -    pkt->buf = local_pkt.buf;
>> -    pkt->side_data       = local_pkt.side_data;
>> -    pkt->side_data_elems = local_pkt.side_data_elems;
>> +    if (!interleave) {
>> +        ret = av_write_frame(dst, pkt);
>> +        /* We only have to backup and restore the fields that
>> +         * we changed ourselves, because av_write_frame() does not
>> +         * modify the packet given to it. */
>> +        pkt->pts          = pts;
>> +        pkt->dts          = dts;
>> +        pkt->duration     = duration;
>> +        pkt->stream_index = stream_index;
>> +        pkt->time_base    = time_base;
>> +    } else
>> +        ret = av_interleaved_write_frame(dst, pkt);
>> +
>>  return ret;
>>  }
>>  
>> diff --git a/libavformat/segment.c b/libavformat/segment.c
>> index ed671353d0..7c171b6fa4 100644
>> --- a/libavformat/segment.c
>> +++ b/libavformat/segment.c
>> @@ -952,7 +952,9 @@ calc_times:
>>  seg->initial_offset || seg->reset_timestamps || seg->avf->oformat->interleave_packet);
>>  
>>  fail:
>> -    if (pkt->stream_index == seg->reference_stream_index) {
>> +    /* Use st->index here as the packet returned from ff_write_chained()
>> +     * is blank if interleaving has been used. */
>> +    if (st->index == seg->reference_stream_index) {
>>  seg->frame_count++;
>>  seg->segment_frame_count++;
>>  }
>>
> Will apply tomorrow unless there are objections.
>

Messy to read, but LGTM.


More information about the ffmpeg-devel mailing list