PR #25671 opened by michaelni
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25671
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25671.patch

Make get_curr_st() return NULL when neither a trak nor a HEIF item is
being parsed, and use it in the readers that rewrite the codec id.

Fixes: heap-buffer-overflow in libopus_decode()
Fixes: mov/esds-outside-trak.mp4
Fixes: JWOFdec1AfGI
Fixes: #24931
Reported-by: Thomas Guilbert <[email protected]>


>From e656240e28b4811001d30871c771907cc29c8687 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Sun, 1 Nov 2026 14:34:12 +0100
Subject: [PATCH 1/2] avformat/mov: ignore sample description atoms outside a
 trak

Make get_curr_st() return NULL when neither a trak nor a HEIF item is
being parsed, and use it in the readers that rewrite the codec id.

Fixes: heap-buffer-overflow in libopus_decode()
Fixes: mov/esds-outside-trak.mp4
Fixes: JWOFdec1AfGI
Fixes: #24931
Reported-by: Thomas Guilbert <[email protected]>
---
 libavformat/mov.c | 36 +++++++++++++++++++++---------------
 1 file changed, 21 insertions(+), 15 deletions(-)

diff --git a/libavformat/mov.c b/libavformat/mov.c
index 786994699e7..8c4b459940c 100644
--- a/libavformat/mov.c
+++ b/libavformat/mov.c
@@ -212,7 +212,8 @@ static HEIFItem *get_heif_item(MOVContext *c, unsigned id)
 
 /**
  * Get the current stream in the parsing process. This can either be the
- * latest stream added to the context, or the stream referenced by an item.
+ * stream of the trak being parsed, or the stream referenced by an item.
+ * Returns NULL when neither a trak nor an item is being parsed.
  */
 static AVStream *get_curr_st(MOVContext *c)
 {
@@ -222,8 +223,13 @@ static AVStream *get_curr_st(MOVContext *c)
     if (c->fc->nb_streams < 1)
         return NULL;
 
-    if (c->cur_item_id == -1)
+    if (c->cur_item_id == -1) {
+        if (c->trak_index < 0) {
+            av_log(c->fc, AV_LOG_WARNING, "Sample description atom outside 
TRAK\n");
+            return NULL;
+        }
         return c->fc->streams[c->fc->nb_streams-1];
+    }
 
     item = get_heif_item(c, c->cur_item_id);
     if (item)
@@ -865,6 +871,8 @@ static int mov_read_hdlr(MOVContext *c, AVIOContext *pb, 
MOVAtom atom)
 
 static int mov_read_esds(MOVContext *c, AVIOContext *pb, MOVAtom atom)
 {
+    if (!get_curr_st(c))
+        return 0;
     return ff_mov_read_esds(c->fc, pb);
 }
 
@@ -2093,13 +2101,12 @@ static int mov_read_mvhd(MOVContext *c, AVIOContext 
*pb, MOVAtom atom)
     return 0;
 }
 
-static void set_last_stream_little_endian(AVFormatContext *fc)
+static void set_curr_stream_little_endian(MOVContext *c)
 {
-    AVStream *st;
+    AVStream *st = get_curr_st(c);
 
-    if (fc->nb_streams < 1)
+    if (!st)
         return;
-    st = fc->streams[fc->nb_streams-1];
 
     switch (st->codecpar->codec_id) {
     case AV_CODEC_ID_PCM_S16BE:
@@ -2127,7 +2134,7 @@ static int mov_read_enda(MOVContext *c, AVIOContext *pb, 
MOVAtom atom)
     int little_endian = avio_rb16(pb) & 0xFF;
     av_log(c->fc, AV_LOG_TRACE, "enda %d\n", little_endian);
     if (little_endian == 1)
-        set_last_stream_little_endian(c->fc);
+        set_curr_stream_little_endian(c);
     return 0;
 }
 
@@ -2158,10 +2165,9 @@ static int mov_read_pcmc(MOVContext *c, AVIOContext *pb, 
MOVAtom atom)
     format_flags = avio_r8(pb);
     pcm_sample_size = avio_r8(pb);
 
-    if (fc->nb_streams < 1)
+    st = get_curr_st(c);
+    if (!st)
         return AVERROR_INVALIDDATA;
-
-    st = fc->streams[fc->nb_streams - 1];
     sc = st->priv_data;
 
     if (sc->format == MOV_MP4_FPCM_TAG) {
@@ -2202,7 +2208,7 @@ static int mov_read_pcmc(MOVContext *c, AVIOContext *pb, 
MOVAtom atom)
     }
 
     if (format_flags & 1) // indicates little-endian format. If not present, 
big-endian format is used
-        set_last_stream_little_endian(c->fc);
+        set_curr_stream_little_endian(c);
     st->codecpar->bits_per_coded_sample = 
av_get_bits_per_sample(st->codecpar->codec_id);
 
     return 0;
@@ -3477,9 +3483,9 @@ static int mov_read_stsd(MOVContext *c, AVIOContext *pb, 
MOVAtom atom)
     MOVStreamContext *sc;
     int ret, entries;
 
-    if (c->fc->nb_streams < 1)
+    st = get_curr_st(c);
+    if (!st)
         return 0;
-    st = c->fc->streams[c->fc->nb_streams - 1];
     sc = st->priv_data;
 
     if (sc->extradata) {
@@ -7907,9 +7913,9 @@ static int mov_read_frma(MOVContext *c, AVIOContext *pb, 
MOVAtom atom)
     enum AVCodecID id;
     AVStream *st;
 
-    if (c->fc->nb_streams < 1)
+    st = get_curr_st(c);
+    if (!st)
         return 0;
-    st = c->fc->streams[c->fc->nb_streams - 1];
     sc = st->priv_data;
 
     switch (sc->format)
-- 
2.54.0


>From 104bd435247b37693f8d4d2beaee2027343ca4fe Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Sun, 1 Nov 2026 14:34:12 +0100
Subject: [PATCH 2/2] avformat/demux: pick the replacement probe decoder like
 try_decode_frame()

codec_close() allocates the new context for the decoder returned by
avcodec_find_decoder(), while try_decode_frame() reopens it with
find_probe_decoder(), which honours the decoder forced through
AVFormatContext.audio_codec and friends as well as the h264 special
case. When the two differ, avcodec_open2() rejects the context as
allocated for another decoder and the stream is not probed further.
---
 libavformat/demux.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/libavformat/demux.c b/libavformat/demux.c
index 359ae2612bc..1468726b096 100644
--- a/libavformat/demux.c
+++ b/libavformat/demux.c
@@ -186,7 +186,7 @@ static int init_input(AVFormatContext *s, const char 
*filename,
                                   s, 0, s->format_probesize);
 }
 
-static int codec_close(FFStream *sti);
+static int codec_close(AVFormatContext *s, FFStream *sti);
 
 static int update_stream_avctx(AVFormatContext *s)
 {
@@ -200,7 +200,7 @@ static int update_stream_avctx(AVFormatContext *s)
 
         if (avcodec_is_open(sti->avctx)) {
             av_log(s, AV_LOG_DEBUG, "Demuxer context update while decoder is 
open, closing and trying to re-open\n");
-            ret = codec_close(sti);
+            ret = codec_close(s, sti);
             sti->info->found_decoder = 0;
             if (ret < 0)
                 return ret;
@@ -1360,7 +1360,7 @@ static int64_t ts_to_samples(AVStream *st, int64_t ts)
     return av_rescale(ts, st->time_base.num * st->codecpar->sample_rate, 
st->time_base.den);
 }
 
-static int codec_close(FFStream *sti)
+static int codec_close(AVFormatContext *s, FFStream *sti)
 {
     AVCodecContext *avctx_new = NULL;
     AVCodecParameters *par_tmp = NULL;
@@ -1369,7 +1369,7 @@ static int codec_close(FFStream *sti)
 
     new_codec =
       (sti->avctx->codec_id != sti->pub.codecpar->codec_id) ?
-      avcodec_find_decoder(sti->pub.codecpar->codec_id) :
+      find_probe_decoder(s, &sti->pub, sti->pub.codecpar->codec_id) :
       sti->avctx->codec;
 
     avctx_new = avcodec_alloc_context3(new_codec);
@@ -1451,7 +1451,7 @@ static int read_frame_internal(AVFormatContext *s, 
AVPacket *pkt)
         if (sti->need_context_update) {
             if (avcodec_is_open(sti->avctx)) {
                 av_log(s, AV_LOG_DEBUG, "Demuxer context update while decoder 
is open, closing and trying to re-open\n");
-                ret = codec_close(sti);
+                ret = codec_close(s, sti);
                 sti->info->found_decoder = 0;
                 if (ret < 0)
                     return ret;
@@ -3274,7 +3274,7 @@ find_stream_info_err:
         }
 
         if (avcodec_is_open(sti->avctx)) {
-            err = codec_close(sti);
+            err = codec_close(ic, sti);
             if (err < 0 && ret >= 0)
                 ret = err;
         }
-- 
2.54.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to