PR #25037 opened by StressTestor URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25037 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25037.patch
# Summary of changes Commits: `avformat/wtvdec: fail on a short read of a legacy attribute GUID`, `avformat/mpegts: stop the raw PCR readahead on a short read`. Two demuxers read a fixed number of bytes into a stack buffer with avio_read() and use the buffer whether or not the read completed. On a truncated file the bytes are never written: - wtvdec formats the 16-byte GUID attribute into a metadata string, so ffprobe prints the uninitialised stack contents as a GUID tag. - mpegts with `-compute_pcr 1` parses the 12-byte PCR lookahead buffer; garbage that parses as a PCR becomes `pcr_incr`, which is published as the packet duration and accumulated into the pts of every later packet. Both are visible on a build with `-ftrivial-auto-var-init=pattern`: ``` ffprobe -show_format wtv-guid-short-read.wtv TAG:encoder=aaaaaaaa-aaaa-aaaa-aaaaaaaaaaaaaaaa (before) (no encoder tag from the truncated entry) (after) ffprobe -f mpegtsraw -compute_pcr 1 -show_entries packet=duration -of csv mpegts-pcr-short-read.ts last packet duration=1716393918470 (before) last packet duration=27000000 (after; same as the untruncated file) ``` The mpegts fix stops the lookahead with `break` rather than failing the packet: the packet was already read successfully, and falling back to the previous increment is the existing, documented behaviour when no next PCR is found. `-compute_pcr 1` output is byte-identical before and after for the lavf-ts FATE file and for the reproducer, both from a file and from a pipe. The two reproducer files are small and generated by a script; happy to attach them or provide them for fate-suite if wanted. 🤖 Generated with [Claude Code](https://claude.com/claude-code) From f61e911d27dc294eafdfb745caafa08e57d6b38c Mon Sep 17 00:00:00 2001 From: StressTestor <[email protected]> Date: Sun, 11 Oct 2026 22:26:31 -0600 Subject: [PATCH 1/2] avformat/wtvdec: fail on a short read of a legacy attribute GUID get_tag() reads a 16-byte GUID attribute into a stack buffer with avio_read() and ignores the return value. On a legacy_attrib entry that is cut inside the GUID, the 16 bytes are never written and the uninitialised stack contents are formatted with PRI_PRETTY_GUID and published as a metadata string; ffprobe prints them as e.g. "TAG:encoder=aaaaaaaa-aaaa-aaaa-aaaaaaaaaaaaaaaa" on a build with -ftrivial-auto-var-init=pattern. Use ff_get_guid(), which reads with ffio_read_size() and fails on a short read, and return without adding the tag, as get_tag() already does when a string attribute is empty. 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/wtvdec.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libavformat/wtvdec.c b/libavformat/wtvdec.c index adea4d9d39..e11b00937d 100644 --- a/libavformat/wtvdec.c +++ b/libavformat/wtvdec.c @@ -496,7 +496,8 @@ static void get_tag(AVFormatContext *s, AVIOContext *pb, const char *key, int ty snprintf(buf, sizeof(buf), "%u", avio_rl16(pb)); } else if (type == 6 && length == 16) { ff_asf_guid guid; - avio_read(pb, guid, 16); + if (ff_get_guid(pb, &guid) < 0) + return; snprintf(buf, sizeof(buf), PRI_PRETTY_GUID, ARG_PRETTY_GUID(guid)); } else if (type == 2 && !strcmp(key, "WM/Picture")) { get_attachment(s, pb, length); -- 2.52.0 From 1478a420686021b00bdfcdd440df4d2d05833b19 Mon Sep 17 00:00:00 2001 From: StressTestor <[email protected]> Date: Sun, 11 Oct 2026 22:26:31 -0600 Subject: [PATCH 2/2] avformat/mpegts: stop the raw PCR readahead on a short read mpegts_raw_read_packet() with compute_pcr looks ahead up to MAX_PACKET_READAHEAD packets for the next PCR, reading 12 bytes of each candidate into a stack buffer with avio_read() and ignoring the return value. When the lookahead runs past the end of the file the buffer keeps whatever it held, uninitialised on the first iteration, and parse_pcr() is run on it. If those bytes happen to look like a PCR, the garbage difference becomes pcr_incr and is published as the duration of this packet and the pts of every later one. On a build with -ftrivial-auto-var-init=pattern a file ending in a partial packet yields duration=1716393918470 instead of 27000000. Stop the readahead when the seek or the read fails. The packet itself was already read successfully, and the "no next PCR found" fallback to the previous increment is the designed behaviour for this case, so failing the packet would be wrong. 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/mpegts.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libavformat/mpegts.c b/libavformat/mpegts.c index c013452e97..8c7df85173 100644 --- a/libavformat/mpegts.c +++ b/libavformat/mpegts.c @@ -3673,8 +3673,9 @@ static int mpegts_raw_read_packet(AVFormatContext *s, AVPacket *pkt) /* we read the next PCR (XXX: optimize it by using a bigger buffer */ pos = avio_tell(s->pb); for (i = 0; i < MAX_PACKET_READAHEAD; i++) { - avio_seek(s->pb, pos + i * ts->raw_packet_size, SEEK_SET); - avio_read(s->pb, pcr_buf, 12); + if (avio_seek(s->pb, pos + i * ts->raw_packet_size, SEEK_SET) < 0 || + avio_read(s->pb, pcr_buf, sizeof(pcr_buf)) != sizeof(pcr_buf)) + break; if (parse_pcr(&next_pcr_h, &next_pcr_l, pcr_buf) == 0) { /* XXX: not precise enough */ ts->pcr_incr = -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
