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]
