[FFmpeg-devel] [PATCH] avcodec/v4l2: fix access to priv_data after codec close.
Mark Thompson
sw at jkqxz.net
Wed Oct 18 01:34:01 EEST 2017
On 17/10/17 18:20, Jorge Ramirez-Ortiz wrote:
> A user can close the codec while keeping references to some of the
> capture buffers. When the codec is closed, the structure that keeps
> the contexts, state and the driver file descriptor is freed.
>
> Since access to some of the elements in that structure is required to
> properly release the memory during the buffer unref callbacks, the
> following commit makes sure the unref callback accesses valid memory.
>
> This commit was tested with valgrind:
>
> $ valgrind ffmpeg_g -report -threads 1 -v 55 -y -c:v h264_v4l2m2m \
> -i video.h264 -an -frames:v 100 -f null -
> ---
> libavcodec/v4l2_buffers.c | 17 ++++++++++++++++-
> libavcodec/v4l2_buffers.h | 6 ++++++
> libavcodec/v4l2_m2m.c | 29 ++++++++++++++++++++++++-----
> libavcodec/v4l2_m2m_dec.c | 2 +-
> 4 files changed, 47 insertions(+), 7 deletions(-)
>
> diff --git a/libavcodec/v4l2_buffers.c b/libavcodec/v4l2_buffers.c
> index ba70c5d..80e65ca 100644
> --- a/libavcodec/v4l2_buffers.c
> +++ b/libavcodec/v4l2_buffers.c
> @@ -205,9 +205,24 @@ static enum AVColorTransferCharacteristic v4l2_get_color_trc(V4L2Buffer *buf)
> static void v4l2_free_buffer(void *opaque, uint8_t *unused)
> {
> V4L2Buffer* avbuf = opaque;
> - V4L2m2mContext *s = buf_to_m2mctx(avbuf);
> + V4L2m2mContext *s = avbuf->m2m ? avbuf->m2m : buf_to_m2mctx(avbuf);
>
> atomic_fetch_sub_explicit(&s->refcount, 1, memory_order_acq_rel);
> +
> + if (avbuf->m2m) {
> + if (!atomic_load(&s->refcount)) {
> + /* unmmap and free the associated buffers */
> + ff_v4l2_context_release(&s->capture);
> +
> + /* close the v4l2 driver */
> + close(s->fd);
> +
> + /* release the duplicated m2m context */
> + av_freep(&s);
> + }
> + return;
> + }
> +
> if (s->reinit) {
> if (!atomic_load(&s->refcount))
> sem_post(&s->refsync);
> diff --git a/libavcodec/v4l2_buffers.h b/libavcodec/v4l2_buffers.h
> index e28a4a6..d774480 100644
> --- a/libavcodec/v4l2_buffers.h
> +++ b/libavcodec/v4l2_buffers.h
> @@ -41,6 +41,12 @@ typedef struct V4L2Buffer {
> /* each buffer needs to have a reference to its context */
> struct V4L2Context *context;
>
> + /* when the codec is closed while the user has buffer references we
> + * still need access to context data (this is a pointer to a duplicated
> + * m2m context created during the codec close function).
> + */
> + struct V4L2m2mContext *m2m;
> +
> /* keep track of the mmap address and mmap length */
> struct V4L2Plane_info {
> int bytesperline;
> diff --git a/libavcodec/v4l2_m2m.c b/libavcodec/v4l2_m2m.c
> index 1d7a852..91b1bbb 100644
> --- a/libavcodec/v4l2_m2m.c
> +++ b/libavcodec/v4l2_m2m.c
> @@ -313,8 +313,8 @@ error:
>
> int ff_v4l2_m2m_codec_end(AVCodecContext *avctx)
> {
> - V4L2m2mContext* s = avctx->priv_data;
> - int ret;
> + V4L2m2mContext *m2m, *s = avctx->priv_data;
> + int i, ret;
>
> ret = ff_v4l2_context_set_status(&s->output, VIDIOC_STREAMOFF);
> if (ret)
> @@ -325,12 +325,31 @@ int ff_v4l2_m2m_codec_end(AVCodecContext *avctx)
> av_log(avctx, AV_LOG_ERROR, "VIDIOC_STREAMOFF %s\n", s->capture.name);
>
> ff_v4l2_context_release(&s->output);
> + sem_destroy(&s->refsync);
>
> - if (atomic_load(&s->refcount))
> - av_log(avctx, AV_LOG_ERROR, "ff_v4l2m2m_codec_end leaving pending buffers\n");
> + if (atomic_load(&s->refcount)) {
> + av_log(avctx, AV_LOG_DEBUG, "ff_v4l2m2m_codec_end, referenced buffers %d\n", atomic_load(&s->refcount));
> +
> + /* We are about to free the private data while the user still has references to the buffers.
> + * This means that when the user releases those buffers, the v4l2_free_buffer callback will be pointing to free'd memory.
> + * Duplicate the m2m context and update the buffers.
> + */
> + m2m = av_mallocz(sizeof(*m2m));
> + if (m2m) {
> + memcpy(m2m, s, sizeof(V4L2m2mContext));
> + for (i = 0; i < s->capture.num_buffers; i++)
> + s->capture.buffers[i].m2m = m2m;
> +
> + return 0;
> + }
No.
The user can call av_buffer_unref() and therefore v4l2_free_buffer() asynchronously on another thread while this manoeuvre is in progress.
> +
> + /* on ENOMEM let's at least close the v4l2 driver and release the capture memory
> + * so the driver is left in a healthy state.
> + */
> + av_log(avctx, AV_LOG_ERROR, "ff_v4l2m2m_codec_end, referenced buffers not accessible\n");
> + }
>
> ff_v4l2_context_release(&s->capture);
> - sem_destroy(&s->refsync);
>
> /* release the hardware */
> if (close(s->fd) < 0 )
> diff --git a/libavcodec/v4l2_m2m_dec.c b/libavcodec/v4l2_m2m_dec.c
> index 958cdc5..f88f819 100644
> --- a/libavcodec/v4l2_m2m_dec.c
> +++ b/libavcodec/v4l2_m2m_dec.c
> @@ -38,7 +38,7 @@ static int v4l2_try_start(AVCodecContext *avctx)
> V4L2m2mContext *s = avctx->priv_data;
> V4L2Context *const capture = &s->capture;
> V4L2Context *const output = &s->output;
> - struct v4l2_selection selection;
> + struct v4l2_selection selection = { 0 };
This looks like it should be a separate change.
> int ret;
>
> /* 1. start the output process */
>
More information about the ffmpeg-devel
mailing list