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]
