PR #24917 opened by michaelni URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24917 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24917.patch
Found-by: Joshua Rogers <[email protected]> >From e31b99f4e080cdec9d7a30bba8b6e5ed52986a75 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Tue, 6 Oct 2026 07:08:45 +0200 Subject: [PATCH 01/10] avformat/dashdec: Skip LCEVC grouping for representations without codecs The fix is the one recommended in the report. Fixes: NULL pointer dereference Fixes: XmvwOQImSoeX Fixes: AISLE-2026-0111-00259 NULL pointer dereference Replicated through UnModified FFmpeg (ffprobe) with UBSAN Found-by: Joshua Rogers <[email protected]> --- libavformat/dashdec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libavformat/dashdec.c b/libavformat/dashdec.c index f3985788f4..ef33ff5ff3 100644 --- a/libavformat/dashdec.c +++ b/libavformat/dashdec.c @@ -2287,7 +2287,7 @@ static int dash_read_header(AVFormatContext *s) for (i = 0; i < c->n_videos; i++) { struct representation *ref; rep = c->videos[i]; - if (!rep->dependencyid || !rep->nb_assoc_stream) + if (!rep->dependencyid || !rep->codecs || !rep->nb_assoc_stream) continue; for (j = 0; j < c->n_videos; j++) { if (j == i) -- 2.52.0 >From 32bb629abd281903408e802f2f3ee3a15ea272c8 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Tue, 6 Oct 2026 07:08:54 +0200 Subject: [PATCH 02/10] avformat/dashdec: Check that the referenced representation has an id Fixes: NULL pointer dereference Fixes: pBjbWIWDyAJK NULL pointer dereference Replicated through UnModified FFmpeg (ffprobe) with UBSAN Found during triage of the security report XmvwOQImSoeX --- libavformat/dashdec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libavformat/dashdec.c b/libavformat/dashdec.c index ef33ff5ff3..0911bd73fa 100644 --- a/libavformat/dashdec.c +++ b/libavformat/dashdec.c @@ -2296,7 +2296,7 @@ static int dash_read_header(AVFormatContext *s) if (!ref->nb_assoc_stream) continue; const AVDictionaryEntry *id = av_dict_get(ref->assoc_stream[0]->metadata, "id", NULL, AV_DICT_MATCH_CASE); - if (!strcmp(rep->dependencyid, id->value)) + if (id && !strcmp(rep->dependencyid, id->value)) break; } if (j >= c->n_videos || !av_strstart(rep->codecs, "lvc1", NULL) || -- 2.52.0 >From 20fa4107e6d114cc1222a392677d65eb7a843012 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Tue, 6 Oct 2026 07:10:44 +0200 Subject: [PATCH 03/10] avformat/imf_cpl: Do not free the marker label on scope allocation failure Fixes: double free Fixes: JLKmGHU1qVX5 Fixes: AISLE-2026-0111-00365 Double free Replicated through API with ASAN and an injected libxml2 allocation failure Found-by: Joshua Rogers <[email protected]> --- libavformat/imf_cpl.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libavformat/imf_cpl.c b/libavformat/imf_cpl.c index 8c3530f412..dba502007c 100644 --- a/libavformat/imf_cpl.c +++ b/libavformat/imf_cpl.c @@ -311,10 +311,8 @@ static int fill_marker(xmlNodePtr marker_elem, FFIMFMarker *marker) if (!(marker->scope_utf8 = xmlGetNoNsProp(element, "scope"))) { marker->scope_utf8 = xmlCharStrdup("http://www.smpte-ra.org/schemas/2067-3/2013#standard-markers"); - if (!marker->scope_utf8) { - xmlFree(marker->label_utf8); + if (!marker->scope_utf8) return AVERROR(ENOMEM); - } } return ret; -- 2.52.0 >From 6b4990daaf0432e296ac2069ed12adf5658d4557 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Tue, 6 Oct 2026 07:09:14 +0200 Subject: [PATCH 04/10] avformat/ttmlenc: Escape the language in the xml:lang attribute The fix is the one recommended in the report. Fixes: XML injection Fixes: NK8mSjRM3lQh Fixes: AISLE-2026-0111-00112 XML injection Replicated through UnModified FFmpeg Found-by: Joshua Rogers <[email protected]> --- libavformat/ttmlenc.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/libavformat/ttmlenc.c b/libavformat/ttmlenc.c index 1ffbd66f80..21cc27727d 100644 --- a/libavformat/ttmlenc.c +++ b/libavformat/ttmlenc.c @@ -34,6 +34,7 @@ #include "ttmlenc.h" #include "libavcodec/ttmlenc.h" #include "libavutil/internal.h" +#include "libavutil/mem.h" enum TTMLPacketType { PACKET_TYPE_PARAGRAPH, @@ -129,7 +130,6 @@ static int ttml_write_header(AVFormatContext *ctx) const AVDictionaryEntry *lang = av_dict_get(st->metadata, "language", NULL, 0); - const char *printed_lang = (lang && lang->value) ? lang->value : ""; ttml_ctx->document_written = 0; ttml_ctx->input_type = ff_is_ttml_stream_paragraph_based(st->codecpar) ? @@ -140,6 +140,7 @@ static int ttml_write_header(AVFormatContext *ctx) if (ttml_ctx->input_type == PACKET_TYPE_PARAGRAPH) { struct TTMLHeaderParameters header_params; + char *printed_lang; int ret = ttml_set_header_values_from_extradata( st->codecpar, &header_params); if (ret < 0) { @@ -149,10 +150,16 @@ static int ttml_write_header(AVFormatContext *ctx) return ret; } + ret = av_escape(&printed_lang, lang ? lang->value : "", NULL, + AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES); + if (ret < 0) + return ret; + avio_printf(pb, ttml_header_text, header_params.tt_element_params, printed_lang, header_params.pre_body_elements); + av_free(printed_lang); } return 0; -- 2.52.0 >From f565da2b27f1d57dd6e7edad64a54db35db1b2f7 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Tue, 6 Oct 2026 07:10:10 +0200 Subject: [PATCH 05/10] avformat/webmdashenc: Escape the language in the lang attribute The fix is the one recommended in the report. Fixes: XML injection Fixes: f81gsd1fPSfe Fixes: AISLE-2026-0111-00553 XML injection Replicated through UnModified FFmpeg Found-by: Joshua Rogers <[email protected]> --- libavformat/webmdashenc.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/libavformat/webmdashenc.c b/libavformat/webmdashenc.c index 3d9badc0d3..54fac45cae 100644 --- a/libavformat/webmdashenc.c +++ b/libavformat/webmdashenc.c @@ -333,8 +333,15 @@ static int write_adaptation_set(AVFormatContext *s, int as_index) avio_printf(pb, " codecs=\"%s\"", get_codec_name(par->codec_id)); lang = av_dict_get(st->metadata, "language", NULL, 0); - if (lang) - avio_printf(pb, " lang=\"%s\"", lang->value); + if (lang) { + char *escaped; + int ret = av_escape(&escaped, lang->value, NULL, AV_ESCAPE_MODE_XML, + AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES); + if (ret < 0) + return ret; + avio_printf(pb, " lang=\"%s\"", escaped); + av_free(escaped); + } if (par->codec_type == AVMEDIA_TYPE_VIDEO && width_in_as) avio_printf(pb, " width=\"%d\"", par->width); -- 2.52.0 >From b9f9c4bf92f02d1fa893ce5c28da28cb01b344f3 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Tue, 6 Oct 2026 07:35:29 +0200 Subject: [PATCH 06/10] avformat/dashenc: Escape the language and role attributes Fixes: XML injection Fixes: qlmAqhtrE25j XML injection Replicated through UnModified FFmpeg Found during triage of the security report f81gsd1fPSfe --- libavformat/dashenc.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/libavformat/dashenc.c b/libavformat/dashenc.c index 6b4ab8eeb9..7c26e6c62f 100644 --- a/libavformat/dashenc.c +++ b/libavformat/dashenc.c @@ -682,8 +682,13 @@ static int write_adaptation_set(AVFormatContext *s, AVIOContext *out, int as_ind avio_printf(out, " par=\"%d:%d\"", as->par.num, as->par.den); } lang = av_dict_get(as->metadata, "language", NULL, 0); - if (lang) - avio_printf(out, " lang=\"%s\"", lang->value); + if (lang) { + char *escaped = xmlescape(lang->value); + if (!escaped) + return AVERROR(ENOMEM); + avio_printf(out, " lang=\"%s\"", escaped); + av_free(escaped); + } avio_printf(out, ">\n"); if (!final && c->ldash && as->max_frag_duration && !(c->profile & MPD_PROFILE_DVB)) @@ -691,8 +696,13 @@ static int write_adaptation_set(AVFormatContext *s, AVIOContext *out, int as_ind if (as->trick_idx >= 0) avio_printf(out, "\t\t\t<EssentialProperty id=\"%d\" schemeIdUri=\"http://dashif.org/guidelines/trickmode\" value=\"%d\"/>\n", as->id, as->trick_idx); role = av_dict_get(as->metadata, "role", NULL, 0); - if (role) - avio_printf(out, "\t\t\t<Role schemeIdUri=\"urn:mpeg:dash:role:2011\" value=\"%s\"/>\n", role->value); + if (role) { + char *escaped = xmlescape(role->value); + if (!escaped) + return AVERROR(ENOMEM); + avio_printf(out, "\t\t\t<Role schemeIdUri=\"urn:mpeg:dash:role:2011\" value=\"%s\"/>\n", escaped); + av_free(escaped); + } if (as->descriptor) avio_printf(out, "\t\t\t%s\n", as->descriptor); for (i = 0; i < s->nb_streams; i++) { -- 2.52.0 >From 7ee9a93c89f2f9d2276035bd35310f974ba5e99a Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Tue, 6 Oct 2026 23:40:27 +0200 Subject: [PATCH 07/10] avformat/webmdashenc: Escape the metadata written into the manifest Fixes: XML injection Fixes: wQScoVJMhVGd XML injection Replicated through UnModified FFmpeg Found during triage of the security report f81gsd1fPSfe --- libavformat/webmdashenc.c | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/libavformat/webmdashenc.c b/libavformat/webmdashenc.c index 54fac45cae..63dbe28bb1 100644 --- a/libavformat/webmdashenc.c +++ b/libavformat/webmdashenc.c @@ -61,6 +61,7 @@ typedef struct WebMDashMuxContext { char *utc_timing_url; double time_shift_buffer_depth; int minimum_update_period; + AVDictionary **escaped_metadata; } WebMDashMuxContext; static const char *get_codec_name(int codec_id) @@ -179,7 +180,8 @@ static int write_representation(AVFormatContext *s, AVStream *st, char *id, WebMDashMuxContext *w = s->priv_data; AVIOContext *pb = s->pb; const AVCodecParameters *par = st->codecpar; - AVDictionaryEntry *bandwidth = av_dict_get(st->metadata, BANDWIDTH, NULL, 0); + AVDictionary *metadata = w->escaped_metadata[st->index]; + AVDictionaryEntry *bandwidth = av_dict_get(metadata, BANDWIDTH, NULL, 0); const char *bandwidth_str; avio_printf(pb, "<Representation id=\"%s\"", id); if (bandwidth) { @@ -207,10 +209,10 @@ static int write_representation(AVFormatContext *s, AVStream *st, char *id, avio_printf(pb, " startsWithSAP=\"1\""); avio_printf(pb, ">"); } else { - AVDictionaryEntry *irange = av_dict_get(st->metadata, INITIALIZATION_RANGE, NULL, 0); - AVDictionaryEntry *cues_start = av_dict_get(st->metadata, CUES_START, NULL, 0); - AVDictionaryEntry *cues_end = av_dict_get(st->metadata, CUES_END, NULL, 0); - AVDictionaryEntry *filename = av_dict_get(st->metadata, FILENAME, NULL, 0); + AVDictionaryEntry *irange = av_dict_get(metadata, INITIALIZATION_RANGE, NULL, 0); + AVDictionaryEntry *cues_start = av_dict_get(metadata, CUES_START, NULL, 0); + AVDictionaryEntry *cues_end = av_dict_get(metadata, CUES_END, NULL, 0); + AVDictionaryEntry *filename = av_dict_get(metadata, FILENAME, NULL, 0); if (!irange || !cues_start || !cues_end || !filename) return AVERROR(EINVAL); @@ -365,7 +367,7 @@ static int write_adaptation_set(AVFormatContext *s, int as_index) if (w->is_live) { AVDictionaryEntry *filename = - av_dict_get(st->metadata, FILENAME, NULL, 0); + av_dict_get(w->escaped_metadata[st->index], FILENAME, NULL, 0); char *underscore_pos, *period_pos; int ret; if (!filename) @@ -393,7 +395,7 @@ static int write_adaptation_set(AVFormatContext *s, int as_index) int ret; if (w->is_live) { AVDictionaryEntry *filename = - av_dict_get(st->metadata, FILENAME, NULL, 0); + av_dict_get(w->escaped_metadata[st->index], FILENAME, NULL, 0); if (!filename) return AVERROR(EINVAL); ret = split_filename(filename->value, &underscore_pos, &period_pos); @@ -497,6 +499,24 @@ static int webm_dash_manifest_write_header(AVFormatContext *s) return AVERROR(EINVAL); } + w->escaped_metadata = av_calloc(s->nb_streams, sizeof(*w->escaped_metadata)); + if (!w->escaped_metadata) + return AVERROR(ENOMEM); + for (i = 0; i < s->nb_streams; i++) { + const AVDictionaryEntry *e = NULL; + while ((e = av_dict_iterate(s->streams[i]->metadata, e))) { + char *value; + ret = av_escape(&value, e->value, NULL, AV_ESCAPE_MODE_XML, + AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES); + if (ret < 0) + goto fail; + ret = av_dict_set(&w->escaped_metadata[i], e->key, value, + AV_DICT_DONT_STRDUP_VAL | AV_DICT_MULTIKEY); + if (ret < 0) + goto fail; + } + } + ret = parse_adaptation_sets(s); if (ret < 0) { goto fail; @@ -523,6 +543,9 @@ static int webm_dash_manifest_write_header(AVFormatContext *s) write_footer(s); fail: free_adaptation_sets(s); + for (i = 0; i < s->nb_streams; i++) + av_dict_free(&w->escaped_metadata[i]); + av_freep(&w->escaped_metadata); return ret < 0 ? ret : 0; } -- 2.52.0 >From 36d637a297c9fd5c8b11b5cd5e5abd58ed91b8ae Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Tue, 6 Oct 2026 23:40:45 +0200 Subject: [PATCH 08/10] avformat/webmdashenc: Take the language from the escaped metadata --- libavformat/webmdashenc.c | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/libavformat/webmdashenc.c b/libavformat/webmdashenc.c index 63dbe28bb1..c463c7c665 100644 --- a/libavformat/webmdashenc.c +++ b/libavformat/webmdashenc.c @@ -334,16 +334,9 @@ static int write_adaptation_set(AVFormatContext *s, int as_index) par->codec_type == AVMEDIA_TYPE_VIDEO ? "video" : "audio"); avio_printf(pb, " codecs=\"%s\"", get_codec_name(par->codec_id)); - lang = av_dict_get(st->metadata, "language", NULL, 0); - if (lang) { - char *escaped; - int ret = av_escape(&escaped, lang->value, NULL, AV_ESCAPE_MODE_XML, - AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES); - if (ret < 0) - return ret; - avio_printf(pb, " lang=\"%s\"", escaped); - av_free(escaped); - } + lang = av_dict_get(w->escaped_metadata[st->index], "language", NULL, 0); + if (lang) + avio_printf(pb, " lang=\"%s\"", lang->value); if (par->codec_type == AVMEDIA_TYPE_VIDEO && width_in_as) avio_printf(pb, " width=\"%d\"", par->width); -- 2.52.0 >From 4ec8303b71662cb3e2200d1e642a37199a92c45e Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Wed, 7 Oct 2026 01:27:37 +0200 Subject: [PATCH 09/10] avformat/dashdec: Check the URL size accumulation for overflow The check follows the recommendation in the report. Fixes: lNi0cBMALYfT Fixes: AISLE-2026-0111-00522 Fixes: AISLE-2026-0110-00522 Fixes: AISLE-2026-0109-00522 Found-by: Joshua Rogers <[email protected]> --- libavformat/dashdec.c | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/libavformat/dashdec.c b/libavformat/dashdec.c index 0911bd73fa..26da3f11d2 100644 --- a/libavformat/dashdec.c +++ b/libavformat/dashdec.c @@ -175,9 +175,12 @@ static int ishttp(char *url) return proto_name && av_strstart(proto_name, "http", NULL); } -static int aligned(int val) +static int add_url_size(int *url_size, uint64_t len) { - return ((val + 0x3F) >> 6) << 6; + if (len > INT_MAX - 0x3F - *url_size) + return AVERROR(ERANGE); + *url_size = FFALIGN(*url_size + len, 64); + return 0; } static int64_t get_current_time_in_sec(void) @@ -736,13 +739,14 @@ static int resolve_content_path(AVFormatContext *s, const char *url, int *max_ur int updated = 0; int size = 0; int i; - int tmp_max_url_size = strlen(url); + int tmp_max_url_size = 0; + uint64_t url_len = strlen(url); for (i = n_baseurl_nodes-1; i >= 0 ; i--) { text = xmlNodeGetContent(baseurl_nodes[i]); if (!text) continue; - tmp_max_url_size += strlen(text); + url_len += strlen(text); if (ishttp(text)) { xmlFree(text); break; @@ -750,7 +754,9 @@ static int resolve_content_path(AVFormatContext *s, const char *url, int *max_ur xmlFree(text); } - tmp_max_url_size = aligned(tmp_max_url_size); + updated = add_url_size(&tmp_max_url_size, url_len); + if (updated < 0) + goto end; text = av_mallocz(tmp_max_url_size + 1); if (!text) { updated = AVERROR(ENOMEM); @@ -977,10 +983,12 @@ static int parse_manifest_representation(AVFormatContext *s, const char *url, baseurl_nodes[3] = representation_baseurl_node; ret = resolve_content_path(s, url, &c->max_url_size, baseurl_nodes, 4); - c->max_url_size = aligned(c->max_url_size - + (rep->id ? strlen(rep->id) : 0) - + (rep_bandwidth_val ? strlen(rep_bandwidth_val) : 0)); - if (ret == AVERROR(ENOMEM) || ret == 0) + if (ret <= 0) + goto free; + ret = add_url_size(&c->max_url_size, + (rep->id ? strlen(rep->id) : 0) + + (rep_bandwidth_val ? strlen(rep_bandwidth_val) : 0)); + if (ret < 0) goto free; if (representation_segmenttemplate_node || fragment_template_node || period_segmenttemplate_node) { fragment_timeline_node = NULL; @@ -997,7 +1005,11 @@ static int parse_manifest_representation(AVFormatContext *s, const char *url, xmlFree(val); goto enomem; } - c->max_url_size = aligned(c->max_url_size + strlen(val)); + ret = add_url_size(&c->max_url_size, strlen(val)); + if (ret < 0) { + xmlFree(val); + goto free; + } rep->init_section->url = get_content_url(baseurl_nodes, 4, c->max_url_size, rep->id, rep_bandwidth_val, val); @@ -1008,7 +1020,11 @@ static int parse_manifest_representation(AVFormatContext *s, const char *url, } val = get_val_from_nodes_tab(fragment_templates_tab, 4, "media"); if (val) { - c->max_url_size = aligned(c->max_url_size + strlen(val)); + ret = add_url_size(&c->max_url_size, strlen(val)); + if (ret < 0) { + xmlFree(val); + goto free; + } rep->url_template = get_content_url(baseurl_nodes, 4, c->max_url_size, rep->id, rep_bandwidth_val, val); -- 2.52.0 >From f1a3ef2ea0777783a4ba5a217d0e9680c3772037 Mon Sep 17 00:00:00 2001 From: Joshua Rogers <[email protected]> Date: Wed, 7 Oct 2026 01:27:55 +0200 Subject: [PATCH 10/10] avformat/dash: Do not write into zero sized buffers in ff_dash_fill_tmpl_params() Fixes: lNi0cBMALYfT Fixes: AISLE-2026-0111-00522 Fixes: AISLE-2026-0110-00522 Fixes: AISLE-2026-0109-00522 Found-by: Joshua Rogers <[email protected]> --- libavformat/dash.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libavformat/dash.c b/libavformat/dash.c index fddfa82340..6c27fb5781 100644 --- a/libavformat/dash.c +++ b/libavformat/dash.c @@ -98,6 +98,8 @@ void ff_dash_fill_tmpl_params(char *dst, size_t buffer_size, int64_t time) { int dst_pos = 0; const char *t_cur = template; + if (!buffer_size) + return; while (dst_pos < buffer_size - 1 && *t_cur) { char format_tag[7]; // May be "%d", "%0Xd", or "%0Xlld" (for $Time$), where X is in [0-9] int n = 0; -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
