Crash in InputContainer::__cinit__
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
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.
How can someone reproduce this?
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.
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?
(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.