[FFmpeg-devel] [PATCH] avcodec/h264, videotoolbox: fix use-after-free of AVFrame buffer when VT decoder fails
Aman Gupta
aman at tmm1.net
Tue Feb 21 18:57:56 EET 2017
On Mon, Feb 13, 2017 at 6:04 PM, Aman Gupta <ffmpeg at tmm1.net> wrote:
> From: Aman Gupta <aman at tmm1.net>
>
> The videotoolbox hwaccel returns a dummy frame with a 1-byte buffer from
> alloc_frame(). In end_frame(), this buffer is replaced with the actual
> decoded data from VTDecompressionSessionDecodeFrame(). This is hacky,
> but works well enough, as long as the VT decoder returns a valid frame on
> every end_frame() call.
>
> In the case of errors, things get more interesting. Before
> 9747219958060d8c4f697df62e7f172c2a77e6c7, the dummy 1-byte frame would
> accidentally be returned all the way up to the user. After that commit,
> the dummy frame was properly unref'd so the user received an error.
>
> However, since that commit, VT hwaccel failures started causing random
> segfaults in the h264 decoder. This happened more often on iOS where the
> VT implementation is more likely to throw errors on bitstream anomolies.
> A recent report of this issue can be see in
> http://ffmpeg.org/pipermail/libav-user/2016-November/009831.html
>
> The root cause here is that between the calls to alloc_frame() and
> end_frame(), the h264 decoder will reference the dummy 1-byte frame in
> its ref_list. When the end_frame() call fails, the dummy frame is
> unref'd but still referenced in the h264 decoder. This eventually causes
> a NULL deference and segmentation fault.
>
> This patch fixes the issue by properly discarding references to the
> dummy frame in the H264Context after the frame has been unref'd.
> ---
> libavcodec/h264_picture.c | 3 +++
> libavcodec/h264_refs.c | 20 ++++++++++++++++++--
> libavcodec/h264dec.h | 1 +
> 3 files changed, 22 insertions(+), 2 deletions(-)
>
> diff --git a/libavcodec/h264_picture.c b/libavcodec/h264_picture.c
> index f634d2a..702ca12 100644
> --- a/libavcodec/h264_picture.c
> +++ b/libavcodec/h264_picture.c
> @@ -177,6 +177,9 @@ int ff_h264_field_end(H264Context *h, H264SliceContext
> *sl, int in_setup)
> if (err < 0)
> av_log(avctx, AV_LOG_ERROR,
> "hardware accelerator failed to decode picture\n");
> +
> + if (err < 0 && avctx->hwaccel->pix_fmt == AV_PIX_FMT_VIDEOTOOLBOX)
> + ff_h264_remove_cur_pic_ref(h);
>
This patch fixed the crash I was seeing pretty often, but now another
assert() can trip (albeit less frequently):
Assertion h->cur_pic_ptr->f->buf[0] failed at
src/libavcodec/h264_slice.c:1340
I will try wm4's suggestion of leaving the dummy 1-byte frame in-place, but
ignoring it when it's time to return a frame to the user.
> }
>
> #if FF_API_CAP_VDPAU
> diff --git a/libavcodec/h264_refs.c b/libavcodec/h264_refs.c
> index 97bf588..9c77bc7 100644
> --- a/libavcodec/h264_refs.c
> +++ b/libavcodec/h264_refs.c
> @@ -560,6 +560,23 @@ static H264Picture *remove_long(H264Context *h, int
> i, int ref_mask)
> return pic;
> }
>
> +void ff_h264_remove_cur_pic_ref(H264Context *h)
> +{
> + int j;
> +
> + if (h->short_ref[0] == h->cur_pic_ptr) {
> + remove_short_at_index(h, 0);
> + }
> +
> + if (h->cur_pic_ptr->long_ref) {
> + for (j = 0; j < FF_ARRAY_ELEMS(h->long_ref); j++) {
> + if (h->long_ref[j] == h->cur_pic_ptr) {
> + remove_long(h, j, 0);
> + }
> + }
> + }
> +}
> +
> void ff_h264_remove_all_refs(H264Context *h)
> {
> int i;
> @@ -571,8 +588,7 @@ void ff_h264_remove_all_refs(H264Context *h)
>
> if (h->short_ref_count && !h->last_pic_for_ec.f->data[0]) {
> ff_h264_unref_picture(h, &h->last_pic_for_ec);
> - if (h->short_ref[0]->f->buf[0])
> - ff_h264_ref_picture(h, &h->last_pic_for_ec, h->short_ref[0]);
> + ff_h264_ref_picture(h, &h->last_pic_for_ec, h->short_ref[0]);
> }
>
> for (i = 0; i < h->short_ref_count; i++) {
> diff --git a/libavcodec/h264dec.h b/libavcodec/h264dec.h
> index 5f868b7..063afed 100644
> --- a/libavcodec/h264dec.h
> +++ b/libavcodec/h264dec.h
> @@ -569,6 +569,7 @@ int ff_h264_alloc_tables(H264Context *h);
> int ff_h264_decode_ref_pic_list_reordering(H264SliceContext *sl, void
> *logctx);
> int ff_h264_build_ref_list(H264Context *h, H264SliceContext *sl);
> void ff_h264_remove_all_refs(H264Context *h);
> +void ff_h264_remove_cur_pic_ref(H264Context *h);
>
> /**
> * Execute the reference picture marking (memory management control
> operations).
> --
> 2.10.1 (Apple Git-78)
>
>
More information about the ffmpeg-devel
mailing list