PR #25552 opened by Romain Beauxis (toots)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25552
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25552.patch


>From 88d8ae7b23879cb45e5307f5030ac10633214626 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Tue, 6 Oct 2026 07:33:05 +0200
Subject: [PATCH 1/3] avformat/oggenc: Reset the current page when it cannot be
 buffered

On allocation failure the full page stayed in the stream context, and
the next segment was added past its 255 entry segment table.

Fixes: out of array access
Fixes: flOsaBhTFc6F
Fixes: AISLE-2026-0111-00072
Negative size memset Replicated through API with ASAN and av_max_alloc() 
lowered after the header
Found-by: Joshua Rogers <[email protected]>

(cherry picked from commit 6d40aa17395aaba80922441bcfb58399a88475e3)
---
 libavformat/oggenc.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/libavformat/oggenc.c b/libavformat/oggenc.c
index 5c7e3da0bcd..cee9c012239 100644
--- a/libavformat/oggenc.c
+++ b/libavformat/oggenc.c
@@ -192,8 +192,10 @@ static int ogg_buffer_page(AVFormatContext *s, 
OGGStreamContext *oggstream)
     OGGPageList **p = &ogg->page_list;
     OGGPageList *l = av_mallocz(sizeof(*l));
 
-    if (!l)
+    if (!l) {
+        ogg_reset_cur_page(oggstream);
         return AVERROR(ENOMEM);
+    }
     l->page = oggstream->page;
 
     oggstream->page.start_granule = ogg_granule_to_timestamp(oggstream, 
oggstream->page.granule);
-- 
2.54.0


>From 53f1941354b2b13a00dd072c11bc1c874121ab33 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Wed, 7 Oct 2026 00:03:31 +0200
Subject: [PATCH 2/3] avformat/oggenc: Propagate page buffering errors

Return the error, as recommended in the report AISLE-2026-0111-00072.

Not a security issue.

Fixes: TjbJ8BXPA93x
Found-by: Joshua Rogers <[email protected]>
(cherry picked from commit a659cd842b122e2f41973ac7c4e826426cc0d756)
---
 libavformat/oggenc.c | 60 ++++++++++++++++++++++++++++----------------
 1 file changed, 38 insertions(+), 22 deletions(-)

diff --git a/libavformat/oggenc.c b/libavformat/oggenc.c
index cee9c012239..43317e943ef 100644
--- a/libavformat/oggenc.c
+++ b/libavformat/oggenc.c
@@ -221,7 +221,7 @@ static int ogg_buffer_data(AVFormatContext *s, AVStream *st,
     OGGContext *ogg = s->priv_data;
     int total_segments = size / 255 + 1;
     const uint8_t *p = data;
-    int i, segments, len, flush = 0;
+    int i, segments, len, ret, flush = 0;
 
     // Handles VFR by flushing page because this frame needs to have a 
timestamp
     // For theora and VP8, keyframes also need to have a timestamp to 
correctly mark
@@ -232,15 +232,17 @@ static int ogg_buffer_data(AVFormatContext *s, AVStream 
*st,
         (ogg_granule_to_timestamp(oggstream, granule) >
          ogg_granule_to_timestamp(oggstream, oggstream->last_granule) + 1 ||
          ogg_key_granule(oggstream, granule))) {
-        if (oggstream->page.granule != -1)
-            ogg_buffer_page(s, oggstream);
+        if (oggstream->page.granule != -1 &&
+            (ret = ogg_buffer_page(s, oggstream)) < 0)
+            return ret;
         flush = 1;
     }
 
     // avoid a continued page
     if (!header && oggstream->page.size > 0 &&
         MAX_PAGE_SIZE - oggstream->page.size < size) {
-        ogg_buffer_page(s, oggstream);
+        if ((ret = ogg_buffer_page(s, oggstream)) < 0)
+            return ret;
     }
 
     for (i = 0; i < total_segments; ) {
@@ -275,17 +277,20 @@ static int ogg_buffer_data(AVFormatContext *s, AVStream 
*st,
                                          time_base, AV_TIME_BASE_Q);
 
             if (page->segments_count == 255) {
-                ogg_buffer_page(s, oggstream);
+                if ((ret = ogg_buffer_page(s, oggstream)) < 0)
+                    return ret;
             } else if (!header) {
                 if (ogg->pref_duration > 0 && next - start >= 
ogg->pref_duration) {
-                    ogg_buffer_page(s, oggstream);
+                    if ((ret = ogg_buffer_page(s, oggstream)) < 0)
+                        return ret;
                 }
             }
         }
     }
 
-    if (flush && oggstream->page.granule != -1)
-        ogg_buffer_page(s, oggstream);
+    if (flush && oggstream->page.granule != -1 &&
+        (ret = ogg_buffer_page(s, oggstream)) < 0)
+        return ret;
 
     return 0;
 }
@@ -630,24 +635,33 @@ static int ogg_init(AVFormatContext *s)
 static int ogg_write_header(AVFormatContext *s)
 {
     OGGStreamContext *oggstream = NULL;
-    int i, j;
+    int i, j, ret;
 
     for (j = 0; j < s->nb_streams; j++) {
         oggstream = s->streams[j]->priv_data;
-        ogg_buffer_data(s, s->streams[j], oggstream->header[0],
-                        oggstream->header_len[0], 0, 1);
+        ret = ogg_buffer_data(s, s->streams[j], oggstream->header[0],
+                              oggstream->header_len[0], 0, 1);
+        if (ret < 0)
+            return ret;
         oggstream->page.flags |= 2; // bos
-        ogg_buffer_page(s, oggstream);
+        ret = ogg_buffer_page(s, oggstream);
+        if (ret < 0)
+            return ret;
     }
     for (j = 0; j < s->nb_streams; j++) {
         AVStream *st = s->streams[j];
         oggstream = st->priv_data;
         for (i = 1; i < 3; i++) {
-            if (oggstream->header_len[i])
-                ogg_buffer_data(s, st, oggstream->header[i],
-                                oggstream->header_len[i], 0, 1);
+            if (oggstream->header_len[i]) {
+                ret = ogg_buffer_data(s, st, oggstream->header[i],
+                                      oggstream->header_len[i], 0, 1);
+                if (ret < 0)
+                    return ret;
+            }
         }
-        ogg_buffer_page(s, oggstream);
+        ret = ogg_buffer_page(s, oggstream);
+        if (ret < 0)
+            return ret;
     }
 
     oggstream->page.start_granule = AV_NOPTS_VALUE;
@@ -768,15 +782,16 @@ static int ogg_write_packet_internal(AVFormatContext *s, 
AVPacket *pkt)
 
 static int ogg_write_packet(AVFormatContext *s, AVPacket *pkt)
 {
-    int i;
+    int i, ret;
 
     if (pkt)
         return pkt->size || !pkt->side_data_elems ? 
ogg_write_packet_internal(s, pkt) : 0;
 
     for (i = 0; i < s->nb_streams; i++) {
         OGGStreamContext *oggstream = s->streams[i]->priv_data;
-        if (oggstream->page.segments_count)
-            ogg_buffer_page(s, oggstream);
+        if (oggstream->page.segments_count &&
+            (ret = ogg_buffer_page(s, oggstream)) < 0)
+            return ret;
     }
 
     ogg_write_pages(s, 2);
@@ -785,14 +800,15 @@ static int ogg_write_packet(AVFormatContext *s, AVPacket 
*pkt)
 
 static int ogg_write_trailer(AVFormatContext *s)
 {
-    int i;
+    int i, ret;
 
     /* flush current page if needed */
     for (i = 0; i < s->nb_streams; i++) {
         OGGStreamContext *oggstream = s->streams[i]->priv_data;
 
-        if (oggstream->page.segments_count)
-            ogg_buffer_page(s, oggstream);
+        if (oggstream->page.segments_count &&
+            (ret = ogg_buffer_page(s, oggstream)) < 0)
+            return ret;
     }
 
     ogg_write_pages(s, 1);
-- 
2.54.0


>From 99b632d544941f305595b4ed005a4fa3ad82f0ea Mon Sep 17 00:00:00 2001
From: Martin Mirchev <[email protected]>
Date: Tue, 27 Oct 2026 11:49:49 +0800
Subject: [PATCH 3/3] avformat/oggparsevorbis: fix chained streams with headers
 over 64 KiB

When a chained Vorbis stream starts, its three header packets are
packed into new extradata with 16-bit size prefixes. A header larger
than 65535 bytes, typically a comment header with embedded cover art,
gets its size truncated, so the Vorbis parser cannot be reinitialized
and demuxing fails with "Failed to re-initialize Vorbis parser". The
audio of the new chained stream and everything after it is lost.

Use Xiph lacing instead, as vorbis_header() already does for the
initial extradata.

Signed-off-by: Martin Mirchev <[email protected]>
(cherry picked from commit 94110fbc39a85f442530fc73d5c9b906d2551244)
---
 libavformat/oggparsevorbis.c | 29 ++++++++++++++++++++---------
 1 file changed, 20 insertions(+), 9 deletions(-)

diff --git a/libavformat/oggparsevorbis.c b/libavformat/oggparsevorbis.c
index 4044ad3bf75..71de123a941 100644
--- a/libavformat/oggparsevorbis.c
+++ b/libavformat/oggparsevorbis.c
@@ -458,7 +458,6 @@ static int vorbis_packet(AVFormatContext *s, int idx)
     int duration, flags = 0;
     int skip_packet = 0;
     int ret, new_extradata_size;
-    PutByteContext pb;
 
     if (!priv->vp)
         return AVERROR_INVALIDDATA;
@@ -587,20 +586,32 @@ static int vorbis_packet(AVFormatContext *s, int idx)
     }
 
     if (priv->header && priv->comment && priv->setup) {
-        new_extradata_size = priv->header_size + priv->comment_size + 
priv->setup_size + 6;
+        uint64_t total_size = (uint64_t)priv->header_size + priv->comment_size 
+
+                              priv->setup_size;
+        uint8_t *p;
+
+        /* Use Xiph lacing like vorbis_header() does: 16-bit sizes cannot
+         * describe headers larger than 64 KiB, e.g. a comment header with
+         * embedded cover art. */
+        if (total_size + total_size / 255 + 3 > INT_MAX)
+            return AVERROR_INVALIDDATA;
+        new_extradata_size = 1 + priv->header_size / 255 + 1 +
+                             priv->comment_size / 255 + 1 + total_size;
 
         ret = av_reallocp(&os->new_extradata, new_extradata_size);
         if (ret < 0)
             return ret;
 
         os->new_extradata_size = new_extradata_size;
-        bytestream2_init_writer(&pb, os->new_extradata, new_extradata_size);
-        bytestream2_put_be16(&pb, priv->header_size);
-        bytestream2_put_buffer(&pb, priv->header, priv->header_size);
-        bytestream2_put_be16(&pb, priv->comment_size);
-        bytestream2_put_buffer(&pb, priv->comment, priv->comment_size);
-        bytestream2_put_be16(&pb, priv->setup_size);
-        bytestream2_put_buffer(&pb, priv->setup, priv->setup_size);
+        p     = os->new_extradata;
+        *p++  = 2;
+        p    += av_xiphlacing(p, priv->header_size);
+        p    += av_xiphlacing(p, priv->comment_size);
+        memcpy(p, priv->header, priv->header_size);
+        p    += priv->header_size;
+        memcpy(p, priv->comment, priv->comment_size);
+        p    += priv->comment_size;
+        memcpy(p, priv->setup, priv->setup_size);
 
         av_freep(&priv->header);
         priv->header_size = 0;
-- 
2.54.0

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

Reply via email to