[FFmpeg-devel] [PATCH] avformat/mux: Fix double-free when using AVPacket.opaque_ref
Andreas Rheinhardt
andreas.rheinhardt at outlook.com
Fri Sep 3 18:23:29 EEST 2021
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.
- Andreas
More information about the ffmpeg-devel
mailing list