PR #25475 opened by Kilian (lusoris)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25475
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25475.patch

These patches address GCC 14.4.0 and 16.2.1 -Wformat-truncation diagnostics for 
output paths in libavformat/dashenc.c, fragment paths in 
libavformat/smoothstreamingenc.c and duration tags in libavformat/matroskaenc.c.

What the patch does:

- `avformat/dashenc: enlarge path buffers and check output errors`: enlarges 
component and path buffers, rejects segment names that exceed record limits, 
propagates playlist errors and checks segment, initialization, manifest and 
final flush results.
- `avformat/smoothstreamingenc: enlarge fragment paths and check errors`: 
enlarges temporary paths, retains fixed fragment-record limits, rejects 
oversized names and adds error returns for flushing, header copying and record 
insertion.
- `avformat/matroskaenc: validate duration tag formatting`: rejects nonfinite, 
negative and at least 100-hour durations, allocates the formatted string in the 
trailer and verifies its exact length before writing.

Behaviour change: oversized names, unrepresentable durations and output 
failures return errors; duration formatting adds an allocation. Successful 
output with fitting names and accepted durations is unchanged when allocation 
succeeds.

Base: FFmpeg master 46f23be8b1.

Testing:

- GCC 14.4.0 (`--enable-gpl --enable-vulkan`) and GCC 16.2.1 (`--enable-gpl 
--enable-vulkan --enable-vaapi --enable-libdrm --enable-nvenc`), targets all, 
alltools, examples, testprogs and checkasm: master 46f23be8b1 shows 12 
-Wformat-truncation warnings in libavformat/dashenc.c, 
libavformat/matroskaenc.c, libavformat/smoothstreamingenc.c with each compiler; 
with these patches there are none and no new ones.
- FATE with GCC 16.2.1, tests whose name contains `matroska`, `dash`, `mkv`, 
`webm` or `ismv` (95 tests): the same results on master and with these patches; 
12 fail on both sides only because their samples are missing from my local FATE 
sample copy, all others pass.
- FATE has no test for the smoothstreaming muxer.

Split out of #25111 so that each part reaches the reviewers for its area.



>From 6729680948d904325e22e9868a937afc3143b603 Mon Sep 17 00:00:00 2001
From: lusoris <[email protected]>
Date: Thu, 15 Oct 2026 22:06:36 +0200
Subject: [PATCH 1/3] avformat/dashenc: enlarge path buffers and check output
 errors

GCC 14.4.0 and 16.2.1 report -Wformat-truncation for output paths.
Truncated names can misidentify output files or manifest entries.
Enlarge component and path buffers, and reject segment names that do not
fit the segment record. Return playlist errors through callers and check
segment, initialization, manifest and final flush results. Successful
output whose names already fit is unchanged.

Signed-off-by: lusoris <[email protected]>
---
 libavformat/dashenc.c | 98 +++++++++++++++++++++++++++----------------
 1 file changed, 62 insertions(+), 36 deletions(-)

diff --git a/libavformat/dashenc.c b/libavformat/dashenc.c
index 36f39bd083a..8664cbf8821 100644
--- a/libavformat/dashenc.c
+++ b/libavformat/dashenc.c
@@ -73,9 +73,12 @@ enum {
 #define MPD_PROFILE_DVB  2
 #define DASH_MAX_AVAILABILITY_START_TIME_MS (INT64_MAX / 1000)
 #define DASH_MAX_SUGGESTED_PRESENTATION_DELAY ((int64_t)INT_MAX * AV_TIME_BASE 
+ AV_TIME_BASE - 1)
+#define DASH_COMPONENT_MAX 2048
+#define DASH_PATH_MAX      4096
+#define DASH_SEGMENT_NAME_MAX 1024
 
 typedef struct Segment {
-    char file[1024];
+    char file[DASH_SEGMENT_NAME_MAX];
     int64_t start_pos;
     int range_length, index_length;
     int64_t time;
@@ -108,7 +111,7 @@ typedef struct OutputStream {
     AVCodecParserContext *parser;
     AVCodecContext *parser_avctx;
     int packets_written;
-    char initfile[1024];
+    char initfile[DASH_COMPONENT_MAX];
     int64_t init_start_pos, pos;
     int init_range_length;
     int nb_segments, segments_size, segment_index;
@@ -130,9 +133,9 @@ typedef struct OutputStream {
 
     char codec_str[100];
     int written_len;
-    char filename[1024];
-    char full_path[1024];
-    char temp_path[1024];
+    char filename[DASH_COMPONENT_MAX];
+    char full_path[DASH_PATH_MAX];
+    char temp_path[DASH_PATH_MAX];
     double availability_time_offset;
     AVProducerReferenceTime producer_reference_time;
     char producer_reference_time_str[100];
@@ -375,13 +378,13 @@ static void get_start_index_number(OutputStream *os, 
DASHContext *c,
     }
 }
 
-static void write_hls_media_playlist(OutputStream *os, AVFormatContext *s,
-                                     int representation_id, int final,
-                                     char *prefetch_url) {
+static int write_hls_media_playlist(OutputStream *os, AVFormatContext *s,
+                                    int representation_id, int final,
+                                    char *prefetch_url) {
     DASHContext *c = s->priv_data;
     int timescale = os->ctx->streams[0]->time_base.den;
-    char temp_filename_hls[1024];
-    char filename_hls[1024];
+    char temp_filename_hls[DASH_PATH_MAX];
+    char filename_hls[DASH_PATH_MAX];
     AVDictionary *http_opts = NULL;
     int target_duration = 0;
     int ret = 0;
@@ -394,7 +397,7 @@ static void write_hls_media_playlist(OutputStream *os, 
AVFormatContext *s,
 
     if (!c->hls_playlist || start_index >= os->nb_segments ||
         os->segment_type != SEGMENT_TYPE_MP4)
-        return;
+        return 0;
 
     get_hls_playlist_name(filename_hls, sizeof(filename_hls),
                           c->dirname, representation_id);
@@ -405,8 +408,7 @@ static void write_hls_media_playlist(OutputStream *os, 
AVFormatContext *s,
     ret = dashenc_io_open(s, &c->m3u8_out, temp_filename_hls, &http_opts);
     av_dict_free(&http_opts);
     if (ret < 0) {
-        handle_io_open_error(s, ret, temp_filename_hls);
-        return;
+        return handle_io_open_error(s, ret, temp_filename_hls);
     }
     for (i = start_index; i < os->nb_segments; i++) {
         Segment *seg = os->segments[i];
@@ -438,7 +440,9 @@ static void write_hls_media_playlist(OutputStream *os, 
AVFormatContext *s,
                                 c->single_file ? os->initfile : seg->file,
                                 &prog_date_time, 0);
         if (ret < 0) {
-            av_log(os->ctx, AV_LOG_WARNING, "ff_hls_write_file_entry get 
error\n");
+            av_log(os->ctx, AV_LOG_ERROR, "Failed to write HLS file entry\n");
+            dashenc_io_close(s, &c->m3u8_out, temp_filename_hls);
+            return ret;
         }
     }
 
@@ -450,8 +454,11 @@ static void write_hls_media_playlist(OutputStream *os, 
AVFormatContext *s,
 
     dashenc_io_close(s, &c->m3u8_out, temp_filename_hls);
 
-    if (use_rename)
-        ff_rename(temp_filename_hls, filename_hls, os->ctx);
+    if (use_rename &&
+        (ret = ff_rename(temp_filename_hls, filename_hls, os->ctx)) < 0)
+        return ret;
+
+    return 0;
 }
 
 static int flush_init_segment(AVFormatContext *s, OutputStream *os)
@@ -465,7 +472,7 @@ static int flush_init_segment(AVFormatContext *s, 
OutputStream *os)
 
     os->pos = os->init_range_length = range_length;
     if (!c->single_file) {
-        char filename[1024];
+        char filename[DASH_PATH_MAX];
         snprintf(filename, sizeof(filename), "%s%s", c->dirname, os->initfile);
         dashenc_io_close(s, &os->out, filename);
     }
@@ -514,8 +521,8 @@ static void dash_free(AVFormatContext *s)
     ff_format_io_close(s, &c->http_delete);
 }
 
-static void output_segment_list(OutputStream *os, AVIOContext *out, 
AVFormatContext *s,
-                                int representation_id, int final)
+static int output_segment_list(OutputStream *os, AVIOContext *out, 
AVFormatContext *s,
+                               int representation_id, int final)
 {
     DASHContext *c = s->priv_data;
     int i, start_index, start_number;
@@ -584,9 +591,12 @@ static void output_segment_list(OutputStream *os, 
AVIOContext *out, AVFormatCont
         avio_printf(out, "\t\t\t\t</SegmentList>\n");
     }
     if (!c->lhls || final) {
-        write_hls_media_playlist(os, s, representation_id, final, NULL);
+        int ret = write_hls_media_playlist(os, s, representation_id, final, 
NULL);
+        if (ret < 0)
+            return ret;
     }
 
+    return 0;
 }
 
 static char *xmlescape(const char *str) {
@@ -637,7 +647,7 @@ static int write_adaptation_set(AVFormatContext *s, 
AVIOContext *out, int as_ind
     DASHContext *c = s->priv_data;
     AdaptationSet *as = &c->as[as_index];
     AVDictionaryEntry *lang, *role;
-    int i;
+    int i, ret;
 
     avio_printf(out, "\t\t<AdaptationSet id=\"%d\" contentType=\"%s\" 
startWithSAP=\"1\" segmentAlignment=\"true\" bitstreamSwitching=\"true\"",
                 as->id, as->media_type == AVMEDIA_TYPE_VIDEO ? "video" : 
"audio");
@@ -724,7 +734,8 @@ static int write_adaptation_set(AVFormatContext *s, 
AVIOContext *out, int as_ind
         if (!final && c->ldash && os->gop_size && os->frag_type != 
FRAG_TYPE_NONE && !(c->profile & MPD_PROFILE_DVB) &&
             (os->frag_type != FRAG_TYPE_DURATION || os->frag_duration != 
os->seg_duration))
             avio_printf(out, "\t\t\t\t<Resync dT=\"%"PRId64"\" 
type=\"1\"/>\n", os->gop_size);
-        output_segment_list(os, out, s, i, final);
+        if ((ret = output_segment_list(os, out, s, i, final)) < 0)
+            return ret;
         avio_printf(out, "\t\t\t</Representation>\n");
     }
     avio_printf(out, "\t\t</AdaptationSet>\n");
@@ -987,7 +998,7 @@ static int write_manifest(AVFormatContext *s, int final)
 {
     DASHContext *c = s->priv_data;
     AVIOContext *out;
-    char temp_filename[1024];
+    char temp_filename[DASH_PATH_MAX];
     int ret, i;
     const char *proto = avio_find_protocol_name(s->url);
     int use_rename = proto && !strcmp(proto, "file");
@@ -1108,7 +1119,7 @@ static int write_manifest(AVFormatContext *s, int final)
     }
 
     if (c->hls_playlist) {
-        char filename_hls[1024];
+        char filename_hls[DASH_PATH_MAX];
 
         // Publish master playlist only the configured rate
         if (c->master_playlist_created && (!c->master_publish_rate ||
@@ -1352,7 +1363,7 @@ static int dash_init(AVFormatContext *s)
         AVFormatContext *ctx;
         AVStream *st;
         AVDictionary *opts = NULL;
-        char filename[1024];
+        char filename[DASH_PATH_MAX];
         AVBPrint buffer;
 
         os->bit_rate = s->streams[i]->codecpar->bit_rate;
@@ -1631,7 +1642,10 @@ static int add_segment(OutputStream *os, const char 
*file,
     seg = av_mallocz(sizeof(*seg));
     if (!seg)
         return AVERROR(ENOMEM);
-    av_strlcpy(seg->file, file, sizeof(seg->file));
+    if (av_strlcpy(seg->file, file, sizeof(seg->file)) >= sizeof(seg->file)) {
+        av_free(seg);
+        return AVERROR(ENAMETOOLONG);
+    }
     seg->time = time;
     seg->duration = duration;
     if (seg->time < 0) { // If pts<0, it is expected to be cut away with an 
edit list
@@ -1852,7 +1866,11 @@ static int dash_flush(AVFormatContext *s, int final, int 
stream)
         if (!os->bit_rate && !os->first_segment_bit_rate) {
             os->first_segment_bit_rate = (int64_t) range_length * 8 * 
AV_TIME_BASE / duration;
         }
-        add_segment(os, os->filename, os->start_pts, os->max_pts - 
os->start_pts, os->pos, range_length, index_length, next_exp_index);
+        ret = add_segment(os, os->filename, os->start_pts,
+                          os->max_pts - os->start_pts, os->pos, range_length,
+                          index_length, next_exp_index);
+        if (ret < 0)
+            break;
         av_log(s, AV_LOG_VERBOSE, "Representation %d media segment %d written 
to: %s\n", i, os->segment_index, os->full_path);
 
         os->pos += range_length;
@@ -1872,7 +1890,9 @@ static int dash_flush(AVFormatContext *s, int final, int 
stream)
             OutputStream *os = &c->streams[i];
             if (os->ctx && os->ctx_inited) {
                 int64_t file_size = avio_tell(os->ctx->pb);
-                av_write_trailer(os->ctx);
+                ret = av_write_trailer(os->ctx);
+                if (ret < 0)
+                    break;
                 if (c->global_sidx) {
                     int j, start_index, start_number;
                     int64_t sidx_size = avio_tell(os->ctx->pb) - file_size;
@@ -2107,8 +2127,9 @@ static int dash_write_packet(AVFormatContext *s, AVPacket 
*pkt)
     os->total_pkt_duration += pkt->duration;
     os->last_flags = pkt->flags;
 
-    if (!os->init_range_length)
-        flush_init_segment(s, os);
+    if (!os->init_range_length &&
+        (ret = flush_init_segment(s, os)) < 0)
+        return ret;
 
     //open the output context when the first frame of a segment is ready
     if (!c->single_file && os->packets_written == 1) {
@@ -2136,12 +2157,15 @@ static int dash_write_packet(AVFormatContext *s, 
AVPacket *pkt)
         // before fully written but the manifest is needed so that
         // clients and discover the segment filenames.
         if (c->streaming) {
-            write_manifest(s, 0);
+            if ((ret = write_manifest(s, 0)) < 0)
+                return ret;
         }
 
         if (c->lhls) {
             char *prefetch_url = use_rename ? NULL : os->filename;
-            write_hls_media_playlist(os, s, pkt->stream_index, 0, 
prefetch_url);
+            if ((ret = write_hls_media_playlist(os, s, pkt->stream_index, 0,
+                                                prefetch_url)) < 0)
+                return ret;
         }
     }
 
@@ -2164,7 +2188,7 @@ static int dash_write_packet(AVFormatContext *s, AVPacket 
*pkt)
 static int dash_write_trailer(AVFormatContext *s)
 {
     DASHContext *c = s->priv_data;
-    int i;
+    int i, ret;
 
     if (s->nb_streams > 0) {
         OutputStream *os = &c->streams[0];
@@ -2178,7 +2202,9 @@ static int dash_write_trailer(AVFormatContext *s)
                                          s->streams[0]->time_base,
                                          AV_TIME_BASE_Q);
     }
-    dash_flush(s, 1, -1);
+    ret = dash_flush(s, 1, -1);
+    if (ret < 0)
+        return ret;
 
     if (c->remove_at_exit) {
         for (i = 0; i < s->nb_streams; ++i) {
@@ -2186,7 +2212,7 @@ static int dash_write_trailer(AVFormatContext *s)
             dashenc_delete_media_segments(s, os, os->nb_segments);
             dashenc_delete_segment_file(s, os->initfile);
             if (c->hls_playlist && os->segment_type == SEGMENT_TYPE_MP4) {
-                char filename[1024];
+                char filename[DASH_PATH_MAX];
                 get_hls_playlist_name(filename, sizeof(filename), c->dirname, 
i);
                 dashenc_delete_file(s, filename);
             }
@@ -2194,7 +2220,7 @@ static int dash_write_trailer(AVFormatContext *s)
         dashenc_delete_file(s, s->url);
 
         if (c->hls_playlist && c->master_playlist_created) {
-            char filename[1024];
+            char filename[DASH_PATH_MAX];
             snprintf(filename, sizeof(filename), "%s%s", c->dirname, 
c->hls_master_name);
             dashenc_delete_file(s, filename);
         }
-- 
2.54.0


>From 24b7fe2a0b4d5781b291fcb22afcd5af8ba32667 Mon Sep 17 00:00:00 2001
From: lusoris <[email protected]>
Date: Thu, 15 Oct 2026 22:06:36 +0200
Subject: [PATCH 2/3] avformat/smoothstreamingenc: enlarge fragment paths and
 check errors

GCC 14.4.0 and 16.2.1 report -Wformat-truncation for fragment paths.
Truncated paths can refer to the wrong fragment or metadata file.
Enlarge flush path buffers and reject names that do not fit stored
fragment records. Check fragment flushing, header copying, record
insertion and final flush errors. Successful output with names fitting
the existing records is unchanged.

Signed-off-by: lusoris <[email protected]>
---
 libavformat/smoothstreamingenc.c | 31 +++++++++++++++++++++++--------
 1 file changed, 23 insertions(+), 8 deletions(-)

diff --git a/libavformat/smoothstreamingenc.c b/libavformat/smoothstreamingenc.c
index 118761bfc0b..ca82ab592c1 100644
--- a/libavformat/smoothstreamingenc.c
+++ b/libavformat/smoothstreamingenc.c
@@ -38,6 +38,8 @@
 #include "libavutil/mathematics.h"
 #include "libavutil/uuid.h"
 
+#define ISM_PATH_MAX 2048
+
 typedef struct Fragment {
     int64_t start_time, duration;
     int n;
@@ -453,8 +455,11 @@ static int add_fragment(OutputStream *os, const char 
*file, const char *infofile
     frag = av_mallocz(sizeof(*frag));
     if (!frag)
         return AVERROR(ENOMEM);
-    av_strlcpy(frag->file, file, sizeof(frag->file));
-    av_strlcpy(frag->infofile, infofile, sizeof(frag->infofile));
+    if (av_strlcpy(frag->file, file, sizeof(frag->file)) >= sizeof(frag->file) 
||
+        av_strlcpy(frag->infofile, infofile, sizeof(frag->infofile)) >= 
sizeof(frag->infofile)) {
+        av_free(frag);
+        return AVERROR(ENAMETOOLONG);
+    }
     frag->start_time = start_time;
     frag->duration = duration;
     frag->start_pos = start_pos;
@@ -499,7 +504,8 @@ static int ism_flush(AVFormatContext *s, int final)
 
     for (i = 0; i < s->nb_streams; i++) {
         OutputStream *os = &c->streams[i];
-        char filename[1024], target_filename[1024], header_filename[1024], 
curr_dirname[1024];
+        char filename[ISM_PATH_MAX], target_filename[ISM_PATH_MAX];
+        char header_filename[ISM_PATH_MAX], curr_dirname[1024];
         int64_t size;
         int64_t start_ts, duration, moof_size;
         if (!os->packets_written)
@@ -510,7 +516,9 @@ static int ism_flush(AVFormatContext *s, int final)
         if (ret < 0)
             break;
         os->cur_start_pos = os->tail_pos;
-        av_write_frame(os->ctx, NULL);
+        ret = av_write_frame(os->ctx, NULL);
+        if (ret < 0)
+            break;
         avio_flush(os->ctx->pb);
         os->packets_written = 0;
         if (!os->out || os->tail_out)
@@ -542,12 +550,16 @@ static int ism_flush(AVFormatContext *s, int final)
 
         snprintf(header_filename, sizeof(header_filename), 
"%s/FragmentInfo(%s=%"PRIu64")", os->dirname, os->stream_type_tag, start_ts);
         snprintf(target_filename, sizeof(target_filename), 
"%s/Fragments(%s=%"PRIu64")", os->dirname, os->stream_type_tag, start_ts);
-        copy_moof(s, filename, header_filename, moof_size);
+        ret = copy_moof(s, filename, header_filename, moof_size);
+        if (ret < 0)
+            break;
         ret = ff_rename(filename, target_filename, s);
         if (ret < 0)
             break;
-        add_fragment(os, target_filename, header_filename, start_ts, duration,
-                     os->cur_start_pos, size);
+        ret = add_fragment(os, target_filename, header_filename, start_ts,
+                           duration, os->cur_start_pos, size);
+        if (ret < 0)
+            break;
     }
 
     if (c->window_size || (final && c->remove_at_exit)) {
@@ -605,7 +617,10 @@ static int ism_write_packet(AVFormatContext *s, AVPacket 
*pkt)
 static int ism_write_trailer(AVFormatContext *s)
 {
     SmoothStreamingContext *c = s->priv_data;
-    ism_flush(s, 1);
+    int ret = ism_flush(s, 1);
+
+    if (ret < 0)
+        return ret;
 
     if (c->remove_at_exit) {
         char filename[1024];
-- 
2.54.0


>From b4520d2b32919ab89d55ddecd00538fa135e93e1 Mon Sep 17 00:00:00 2001
From: lusoris <[email protected]>
Date: Thu, 15 Oct 2026 22:06:36 +0200
Subject: [PATCH 3/3] avformat/matroskaenc: validate duration tag formatting

GCC 14.4.0 and 16.2.1 report -Wformat-truncation for the fixed-width
duration tag. Durations outside the representable range can produce
truncated text. Reject nonfinite, negative and at least 100-hour
durations, allocate the formatted string, and verify its exact length
before writing. Accepted durations retain the same format and tag size
when allocation succeeds.

Signed-off-by: lusoris <[email protected]>
---
 libavformat/matroskaenc.c | 27 +++++++++++++++++++++------
 1 file changed, 21 insertions(+), 6 deletions(-)

diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c
index a8408da8c1a..5e424c67e77 100644
--- a/libavformat/matroskaenc.c
+++ b/libavformat/matroskaenc.c
@@ -3463,23 +3463,38 @@ after_cues:
 
             if (track->duration_offset > 0) {
                 double duration_sec = track->duration * av_q2d(st->time_base);
-                char duration_string[DURATION_STRING_LENGTH + 1] = "";
+                char *duration_string;
                 ebml_master simpletag;
 
                 av_log(s, AV_LOG_DEBUG, "stream %d end duration = %" PRIu64 
"\n", i,
                        track->duration);
 
+                if (!isfinite(duration_sec) || duration_sec < 0 ||
+                    duration_sec >= 100 * 3600) {
+                    av_log(s, AV_LOG_ERROR,
+                           "Stream duration cannot be represented in a 
Matroska DURATION tag\n");
+                    return AVERROR(EINVAL);
+                }
+                duration_string = av_asprintf("%02d:%02d:%012.9f",
+                                              (int)duration_sec / 3600,
+                                              ((int)duration_sec / 60) % 60,
+                                              fmod(duration_sec, 60));
+                if (!duration_string)
+                    return AVERROR(ENOMEM);
+                if (strlen(duration_string) != DURATION_STRING_LENGTH - 1) {
+                    av_log(s, AV_LOG_ERROR,
+                           "Stream duration cannot be represented in a 
Matroska DURATION tag\n");
+                    av_free(duration_string);
+                    return AVERROR(EINVAL);
+                }
+
                 avio_seek(tags_bc, track->duration_offset, SEEK_SET);
                 simpletag = start_ebml_master(tags_bc, MATROSKA_ID_SIMPLETAG,
                                               2 + 1 + 8 + 23);
                 put_ebml_string(tags_bc, MATROSKA_ID_TAGNAME, "DURATION");
-
-                snprintf(duration_string, sizeof(duration_string), 
"%02d:%02d:%012.9f",
-                         (int) duration_sec / 3600, ((int) duration_sec / 60) 
% 60,
-                         fmod(duration_sec, 60));
-
                 put_ebml_binary(tags_bc, MATROSKA_ID_TAGSTRING,
                                 duration_string, DURATION_STRING_LENGTH);
+                av_free(duration_string);
                 end_ebml_master(tags_bc, simpletag);
             }
         }
-- 
2.54.0

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

Reply via email to