PyAV icon indicating copy to clipboard operation
PyAV copied to clipboard

Crash in InputContainer::__cinit__

Open zewt opened this issue 4 months ago • 5 comments

I'm seeing a crash inside InputContainer::cinit, in the call to av_dict_free if I pass any options to av.open:

==102753== Thread 97:
==102753== Invalid read of size 4
==102753==    at 0xAD2253D: av_dict_free (in venv/lib/python3.12/site-packages/av.libs/libavutil-a63ffd27.so.59.39.100)
==102753==    by 0x1066D75B: __pyx_pf_2av_9container_5input_14InputContainer___cinit__ (input.c:4121)
==102753==    by 0x1066D75B: __pyx_pw_2av_9container_5input_14InputContainer_1__cinit__ (input.c:3769)
==102753==    by 0x1066D75B: __pyx_tp_new_2av_9container_5input_InputContainer (input.c:7059)
==102753==    by 0x599A76: ??? (in /usr/bin/python3.12)
==102753==    by 0x548F54: _PyObject_MakeTpCall (in /usr/bin/python3.12)
==102753==    by 0xFF1C5A1: __pyx_pf_2av_9container_4core_open (core.c:9158)
==102753==    by 0xFF1C5A1: __pyx_pw_2av_9container_4core_1open (core.c:8752)
==102753==    by 0x5DA965: _PyEval_EvalFrameDefault (in /usr/bin/python3.12)
==102753==    by 0x66C158: ??? (in /usr/bin/python3.12)
==102753==    by 0x65A8D43: ??? (in /usr/lib/python3.12/lib-dynload/_asyncio.cpython-312-x86_64-linux-gnu.so)
==102753==    by 0x65A8B5A: ??? (in /usr/lib/python3.12/lib-dynload/_asyncio.cpython-312-x86_64-linux-gnu.so)
==102753==    by 0x548F54: _PyObject_MakeTpCall (in /usr/bin/python3.12)
==102753==    by 0x6A480B: ??? (in /usr/bin/python3.12)
==102753==    by 0x581E9C: ??? (in /usr/bin/python3.12)
==102753==  Address 0x730dda74696c7073 is not stack'd, malloc'd or (recently) free'd

avformat_find_stream_info can find additional streams, which leads to av_dict_free(&c_options[i]) being out-of-bounds.

https://github.com/FFmpeg/FFmpeg/blob/master/libavformat/demux.c#L2520

zewt avatar Sep 06 '25 05:09 zewt

A workaround can be to set options after opening with container.options.update() instead of passing them to av.open. I'm guessing there are options that you want set during av.open, but it's working for me so far.

zewt avatar Sep 06 '25 05:09 zewt

How can someone reproduce this?

WyattBlue avatar Sep 06 '25 16:09 WyattBlue

Seems like it happens with any MPEG:

ffmpeg -f lavfi -i color=size=320x240:rate=25:color=black -t 1 -an test.mpg

>>> import av
>>> av.open('test.mpg', mode='r', options={'abcd': 'efgh'})

It doesn't discover the stream until format_find_stream_info is called, so nb_streams is 0 before and 1 after.

zewt avatar Sep 06 '25 18:09 zewt


import av

c = av.open("test.mpg", options={"abcd": "efgh"})

print("hello world")

print(c)
print(av.__version__)
c.close()

hello world <av.InputContainer 'test.mpg'> 15.1.0

Umm, okay. What's your version and platform?

WyattBlue avatar Sep 06 '25 18:09 WyattBlue

(Umm, okay. If I'm bothering you with a bug report I'll just use the easy workaround.)

15.1.0, Linux. nb_streams is changing:

#include <stdio.h>
#include <assert.h>

#include <libavformat/avformat.h>

int main(int argc, char **argv)
{
    const char *filename = "test.mpg";

    AVFormatContext *ic = NULL;
    int ret = avformat_open_input(&ic, "test.mpg", NULL, NULL);
    assert(ret == 0);
    printf("before: %i\n", ic->nb_streams);
    avformat_find_stream_info(ic, NULL);
    printf("after: %i\n", ic->nb_streams);

    return 0;
}

shows before: 0, after: 1. The problem is pretty straightforward: InputContainer allocates c_options with size 0, calls avformat_find_stream_info which changes nb_streams to 1, then calls av_dict_free() on an array element that doesn't exist.

zewt avatar Sep 06 '25 18:09 zewt