diff --git a/plugins/publish/publish_node.cpp b/plugins/publish/publish_node.cpp index 367d73f..2b35d49 100644 --- a/plugins/publish/publish_node.cpp +++ b/plugins/publish/publish_node.cpp @@ -222,7 +222,7 @@ private: } if (!(fmt->oformat->flags & AVFMT_NOFILE)) { - if (av_io_open_helper(fmt, url_, opts) < 0) { + if (av_io_open_helper(fmt, url_, &opts) < 0) { av_dict_free(&opts); avformat_free_context(fmt); return false; @@ -245,12 +245,10 @@ private: return true; } - int av_io_open_helper(AVFormatContext* fmt, const std::string& url, AVDictionary* opts) { - if (proto_ == "hls") { - // HLS open is slightly different handled in logic but avio_open2 can take callbacks - return avio_open2(&fmt->pb, url.c_str(), AVIO_FLAG_WRITE, &fmt->interrupt_callback, &opts); - } - return avio_open2(&fmt->pb, url.c_str(), AVIO_FLAG_WRITE, &fmt->interrupt_callback, &opts); + int av_io_open_helper(AVFormatContext* fmt, const std::string& url, AVDictionary** opts) { + // IMPORTANT: options must be passed by pointer-to-pointer so FFmpeg can consume/remove entries safely. + // Passing a copied AVDictionary* leads to use-after-free / double-free when FFmpeg updates the dict. + return avio_open2(&fmt->pb, url.c_str(), AVIO_FLAG_WRITE, &fmt->interrupt_callback, opts); } static int CheckInterrupt(void* opaque) {