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