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

# Summary of changes

Commits: `avformat/flvdec: drop queued extradata on a short read`,
`avformat/paf: reject truncated audio and video blocks`,
`avformat/thp: fail on a short read of the component list`.

- **paf**: a block cut short by the end of the file was still copied into the 
packet. Cutting
  fate-suite `paf/hod1-partial.paf` at 64 evenly spaced points, 16 cuts 
returned a full-size
  video packet whose tail is not in the file. The sample itself ends inside a 
block, so its last
  video packet was built the same way and is no longer returned; 
`tests/ref/fate/paf-demux`
  and `tests/ref/fate/paf-video` each lose that one packet/frame line. 
paf-audio is unchanged.
- **thp**: a file ending inside the 16-byte component list opened with exit 
status 0 and a
  phantom 0x0 video stream (zero means video). Reproduced by cutting fate-suite
  `thp/pikmin2-opening1-partial.thp` with 0 or 1 list bytes present; it now 
fails with
  AVERROR_INVALIDDATA, and the complete sample is unchanged.
- **flvdec**: replacement extradata cut short is freed instead of being queued 
at its full
  declared size. The function still returns 0, so the caller ends the stream 
exactly as before
  and packets queued earlier in the same call are still returned. Only 
reachable on a growing
  file; no reproducer. fate-lavf-flv and the fate-suite flv tests pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)


From dc37ebd499824894a517b1a86a34c330e4122591 Mon Sep 17 00:00:00 2001
From: StressTestor <[email protected]>
Date: Sun, 11 Oct 2026 23:38:10 -0600
Subject: [PATCH 1/3] avformat/flvdec: drop queued extradata on a short read

flv_queue_extradata() allocates the replacement extradata, records
its size, and reads it with avio_read() without checking the return
value. On a static file the avio_feof() check after the next tag
header ends the stream before the buffer is used, but if more data
arrives in between (a growing file), the next packet carries the
zero-tailed buffer as AV_PKT_DATA_NEW_EXTRADATA at the full declared
size.

When the read is short, free the buffer and reset the size, so a
queued pointer always refers to a complete extradata buffer. The
function still returns 0 and the caller takes the same path as
before, so the stream ends exactly as it does today and packets
queued earlier in the same call are still returned.

Same class as c2394a0ff3 (binka), 29f513af78 (fsb), 4b47405e1c (genh).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: StressTestor <[email protected]>
---
 libavformat/flvdec.c | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)

diff --git a/libavformat/flvdec.c b/libavformat/flvdec.c
index e4bc8ebd83..7e7def278e 100644
--- a/libavformat/flvdec.c
+++ b/libavformat/flvdec.c
@@ -983,6 +983,8 @@ static int flv_get_extradata(AVFormatContext *s, AVStream 
*st, int size)
 static int flv_queue_extradata(FLVContext *flv, AVIOContext *pb, int stream,
                                int size, int multitrack)
 {
+    int ret;
+
     if (!size)
         return 0;
 
@@ -993,7 +995,11 @@ static int flv_queue_extradata(FLVContext *flv, 
AVIOContext *pb, int stream,
         if (!flv->new_extradata[stream])
             return AVERROR(ENOMEM);
         flv->new_extradata_size[stream] = size;
-        avio_read(pb, flv->new_extradata[stream], size);
+        ret = avio_read(pb, flv->new_extradata[stream], size);
+        if (ret != size) {
+            av_freep(&flv->new_extradata[stream]);
+            flv->new_extradata_size[stream] = 0;
+        }
     } else {
         int new_count = stream + 1;
 
@@ -1023,7 +1029,11 @@ static int flv_queue_extradata(FLVContext *flv, 
AVIOContext *pb, int stream,
         if (!flv->mt_extradata[stream])
             return AVERROR(ENOMEM);
         flv->mt_extradata_sz[stream] = size;
-        avio_read(pb, flv->mt_extradata[stream], size);
+        ret = avio_read(pb, flv->mt_extradata[stream], size);
+        if (ret != size) {
+            av_freep(&flv->mt_extradata[stream]);
+            flv->mt_extradata_sz[stream] = 0;
+        }
     }
 
     return 0;
-- 
2.52.0


From 8c260309cf80f7db90162aaaef6001b445d04b61 Mon Sep 17 00:00:00 2001
From: StressTestor <[email protected]>
Date: Mon, 12 Oct 2026 12:59:00 -0600
Subject: [PATCH 2/3] avformat/paf: reject truncated audio and video blocks

read_packet() reads each block into the frame buffers with
avio_read() and ignores the return value, then copies the buffers
into the packet unconditionally. A file that ends inside a block
delivers whatever the buffer held before, zeros on the first frame
or the previous frame's bytes afterwards, as packet payload. Cutting
the FATE sample hod1-partial.paf at 64 evenly spaced points, 16 of
the cuts return a full-size video packet whose tail is not in the
file.

Use ffio_read_size() so a short block fails the packet.

hod1-partial.paf itself ends inside a block, so its last video packet
was built the same way and is no longer returned; the paf-demux and
paf-video references lose that packet and its frame. paf-audio is
unchanged.

Same class as c2394a0ff3 (binka), 29f513af78 (fsb), 4b47405e1c (genh).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: StressTestor <[email protected]>
---
 libavformat/paf.c        | 9 +++++++--
 tests/ref/fate/paf-demux | 1 -
 tests/ref/fate/paf-video | 1 -
 3 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/libavformat/paf.c b/libavformat/paf.c
index f072f0dc25..5b58134bac 100644
--- a/libavformat/paf.c
+++ b/libavformat/paf.c
@@ -23,6 +23,7 @@
 #include "libavutil/mem.h"
 #include "libavcodec/paf.h"
 #include "avformat.h"
+#include "avio_internal.h"
 #include "demux.h"
 #include "internal.h"
 
@@ -235,7 +236,9 @@ static int read_packet(AVFormatContext *s, AVPacket *pkt)
             if (offset > p->audio_size - p->buffer_size)
                 return AVERROR_INVALIDDATA;
 
-            avio_read(pb, p->audio_frame + offset, p->buffer_size);
+            ret = ffio_read_size(pb, p->audio_frame + offset, p->buffer_size);
+            if (ret < 0)
+                return ret;
             if (offset == (p->max_audio_blks - 2) * p->buffer_size) {
                 memcpy(p->temp_audio_frame, p->audio_frame, p->audio_size);
                 p->got_audio = 1;
@@ -244,7 +247,9 @@ static int read_packet(AVFormatContext *s, AVPacket *pkt)
             if (offset > p->video_size - p->buffer_size)
                 return AVERROR_INVALIDDATA;
 
-            avio_read(pb, p->video_frame + offset, p->buffer_size);
+            ret = ffio_read_size(pb, p->video_frame + offset, p->buffer_size);
+            if (ret < 0)
+                return ret;
         }
         p->current_frame_block++;
     }
diff --git a/tests/ref/fate/paf-demux b/tests/ref/fate/paf-demux
index a0d1f44843..03b535a938 100644
--- a/tests/ref/fate/paf-demux
+++ b/tests/ref/fate/paf-demux
@@ -165,4 +165,3 @@
 0,        148,        148,        1,   234268, 0xde2bb38e, F=0x0
 0,        149,        149,        1,   227440, 0xb78f1aed, F=0x0
 0,        150,        150,        1,   220692, 0x86026588, F=0x0
-0,        151,        151,        1,   214000, 0x0fdbc796, F=0x0
diff --git a/tests/ref/fate/paf-video b/tests/ref/fate/paf-video
index aa1fa82f0e..86d881fbd7 100644
--- a/tests/ref/fate/paf-video
+++ b/tests/ref/fate/paf-video
@@ -154,4 +154,3 @@
 0,        148,        148,        1,   147456, 0xdf341f10
 0,        149,        149,        1,   147456, 0x5c43a9df
 0,        150,        150,        1,   147456, 0xaf121817
-0,        151,        151,        1,   147456, 0x53ba70bf
-- 
2.52.0


From 0699681057c591c29231d95171b638073aa30794 Mon Sep 17 00:00:00 2001
From: StressTestor <[email protected]>
Date: Sun, 11 Oct 2026 22:57:06 -0600
Subject: [PATCH 3/3] avformat/thp: fail on a short read of the component list

thp_read_header() reads the 16-byte component type list with
avio_read() and ignores the return value. The list lives in the
zero-initialised demuxer context and a zero entry means video, so a
file that ends inside the list passes the "no video component" check
and opens with a phantom 0x0 video stream. Cutting the FATE sample
pikmin2-opening1-partial.thp with 0 or 1 of the 16 list bytes present
opens with exit status 0 and a single thp video stream of size 0x0.

Use ffio_read_size() so the header fails on a truncated list.

Same class as c2394a0ff3 (binka), 29f513af78 (fsb), 4b47405e1c (genh).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: StressTestor <[email protected]>
---
 libavformat/thp.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/libavformat/thp.c b/libavformat/thp.c
index c1a7418a2c..dd6d2508d8 100644
--- a/libavformat/thp.c
+++ b/libavformat/thp.c
@@ -68,7 +68,7 @@ static int thp_read_header(AVFormatContext *s)
     AVIOContext *pb = s->pb;
     int64_t fsize= avio_size(pb);
     uint32_t maxsize;
-    int i;
+    int i, ret;
 
     /* Read the file header.  */
                            avio_rb32(pb); /* Skip Magic.  */
@@ -103,7 +103,9 @@ static int thp_read_header(AVFormatContext *s)
         return AVERROR_INVALIDDATA;
 
     /* Read the list of component types.  */
-    avio_read(pb, thp->components, 16);
+    ret = ffio_read_size(pb, thp->components, 16);
+    if (ret < 0)
+        return ret;
 
     for (i = 0; i < thp->compcount; i++) {
         if (thp->components[i] == 0) {
-- 
2.52.0

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

Reply via email to