Commit 1bfb91609d for ffmpeg

commit 1bfb91609d921d1df949b111edfb43a378ca072e
Author: Romain Beauxis <romain.beauxis@gmail.com>
Date:   Wed Oct 28 16:33:52 2026 -0500

    avformat/oggparsevorbis: prevent out of order or missing chained headers

    Header packets of a chained stream are only buffered in identification,
    comment, setup order. A misplaced packet discards the buffered set with
    a warning, so a missing or misordered header no longer mixes headers
    from different chains into the new extradata. A misplaced comment
    header is skipped without applying its metadata.

    Reserved packet types are ignored, as required by Vorbis I section
    1.3.2, and leave a buffered header set intact.

diff --git a/libavformat/oggparsevorbis.c b/libavformat/oggparsevorbis.c
index d4df9e234b..4ecc5bd8aa 100644
--- a/libavformat/oggparsevorbis.c
+++ b/libavformat/oggparsevorbis.c
@@ -209,9 +209,18 @@ int ff_vorbis_comment(AVFormatContext *as, AVDictionary **m,
  * [framing_flag] = read one bit | Not Used
  */

+enum VorbisPacketType {
+    VORBIS_PACKET_DATA,
+    VORBIS_PACKET_ID,
+    VORBIS_PACKET_COMMENT,
+    VORBIS_PACKET_SETUP,
+    VORBIS_PACKET_RESERVED,
+};
+
 struct oggvorbis_private {
     unsigned int len[3];
     unsigned char *packet[3];
+    enum VorbisPacketType last_packet; ///< type of the last packet seen by vorbis_packet()
     AVVorbisParseContext *vp;
     int64_t bos_page_pos;
     int ts_offset;
@@ -445,6 +454,56 @@ static int vorbis_header(AVFormatContext *s, int idx)
     return 1;
 }

+/**
+ * Check that a packet follows the identification, comment, setup order of a
+ * chained stream's headers, and record it as the last packet.
+ *
+ * An identification header starts a new set. A misplaced packet discards the
+ * buffered headers, so an incomplete or misordered set is never packed.
+ * Reserved packet types are ignored, per Vorbis I §1.3.2.
+ *
+ * @return 0 if the packet is in order, AVERROR_INVALIDDATA otherwise
+ */
+static int vorbis_check_packet_order(AVFormatContext *s,
+                                     struct oggvorbis_private *priv,
+                                     enum VorbisPacketType type)
+{
+    enum VorbisPacketType last = priv->last_packet;
+    const char *error = NULL;
+
+    switch (type) {
+    case VORBIS_PACKET_RESERVED:
+        return 0;
+    case VORBIS_PACKET_DATA:
+        if (last == VORBIS_PACKET_ID || last == VORBIS_PACKET_COMMENT)
+            error = "data packet before the setup header";
+        break;
+    case VORBIS_PACKET_ID:
+        if (last == VORBIS_PACKET_ID || last == VORBIS_PACKET_COMMENT)
+            error = "identification header before the setup header";
+        break;
+    case VORBIS_PACKET_COMMENT:
+        if (last != VORBIS_PACKET_ID)
+            error = "comment header without identification header";
+        break;
+    case VORBIS_PACKET_SETUP:
+        if (last != VORBIS_PACKET_COMMENT)
+            error = "setup header without comment header";
+        break;
+    }
+
+    if (error)
+        av_log(s, AV_LOG_WARNING,
+               "Invalid chained stream headers: %s, discarding them\n", error);
+
+    if (error || type == VORBIS_PACKET_ID)
+        for (int i = 0; i < 3; i++)
+            av_freep(&priv->packet[i]);
+
+    priv->last_packet = error && type != VORBIS_PACKET_ID ? VORBIS_PACKET_DATA : type;
+    return error ? AVERROR_INVALIDDATA : 0;
+}
+
 static int vorbis_packet(AVFormatContext *s, int idx)
 {
     struct ogg *ogg = s->priv_data;
@@ -553,6 +612,8 @@ static int vorbis_packet(AVFormatContext *s, int idx)
      * skipped; the headers are buffered and attached as extradata to the
      * first data packet that follows. */
     if (flags & VORBIS_FLAG_HEADER) {
+        vorbis_check_packet_order(s, priv, VORBIS_PACKET_ID);
+
         ret = vorbis_parse_header(s, s->streams[idx], os->buf + os->pstart, os->psize);
         if (ret < 0)
             return ret;
@@ -568,11 +629,8 @@ static int vorbis_packet(AVFormatContext *s, int idx)
     }

     if (flags & VORBIS_FLAG_COMMENT) {
-        if (!priv->packet[0]) {
-            av_log(s, AV_LOG_WARNING,
-                   "Comment header without identification header, ignoring it\n");
+        if (vorbis_check_packet_order(s, priv, VORBIS_PACKET_COMMENT) < 0)
             return 1;
-        }

         ret = vorbis_update_metadata(s, idx);
         if (ret < 0)
@@ -590,6 +648,9 @@ static int vorbis_packet(AVFormatContext *s, int idx)
     }

     if (flags & VORBIS_FLAG_SETUP) {
+        if (vorbis_check_packet_order(s, priv, VORBIS_PACKET_SETUP) < 0)
+            return 1;
+
         ret = av_reallocp(&priv->packet[2], os->psize);
         if (ret < 0)
             return ret;
@@ -600,6 +661,9 @@ static int vorbis_packet(AVFormatContext *s, int idx)
         skip_packet = 1;
     }

+    if (!skip_packet)
+        vorbis_check_packet_order(s, priv, os->buf[os->pstart] & 1 ?
+                                  VORBIS_PACKET_RESERVED : VORBIS_PACKET_DATA);

     /* All three headers for the new chained stream have been collected;
      * pack them into new extradata to be attached to the next data packet. */
diff --git a/tests/ref/fate/ogg-vorbis-chained-misplaced-headers b/tests/ref/fate/ogg-vorbis-chained-misplaced-headers
index e8cc320427..664b7214cf 100644
--- a/tests/ref/fate/ogg-vorbis-chained-misplaced-headers
+++ b/tests/ref/fate/ogg-vorbis-chained-misplaced-headers
@@ -144,26 +144,26 @@ packet|pts=66496
 packet|pts=67520
 packet|pts=68544
 packet|pts=69568
-packet|pts=70016|tag:title=Ziggy Stardust|tag:artist=The Moog Cookbook|tag:album=The Moog Cookbook Plays The Classic Rock Hits|tag:track=4|side_datum/strings_metadata:side_data_type=Strings Metadata|side_datum/new_extradata:side_data_type=New Extradata
 packet|pts=70144
-packet|pts=70720
-packet|pts=71744
-packet|pts=72768
-packet|pts=73792
-packet|pts=74368
+packet|pts=70272
+packet|pts=70848
+packet|pts=71872
+packet|pts=72896
+packet|pts=73920
 packet|pts=74496
 packet|pts=74624
-packet|pts=75200
-packet|pts=76224
-packet|pts=77248
-packet|pts=78272
-packet|pts=79296
-packet|pts=80320
-packet|pts=81344
-packet|pts=82368
-packet|pts=83392
-packet|pts=84416
-packet|pts=85440
+packet|pts=74752
+packet|pts=75328
+packet|pts=76352
+packet|pts=77376
+packet|pts=78400
+packet|pts=79424
+packet|pts=80448
+packet|pts=81472
+packet|pts=82496
+packet|pts=83520
+packet|pts=84544
+packet|pts=85568
 packet|pts=16320
 packet|pts=17344
 packet|pts=18368