PR #25796 opened by michaelni URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25796 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25796.patch
>From 9e3ad3c7749e761ea88b39bed2eef6f880a8418b Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Tue, 3 Nov 2026 15:25:11 +0100 Subject: [PATCH 1/8] avfilter/vf_vibrance: offset the 8-bit planar source by the source linesize Regression since 725328672a. Fixes: out of array read Fixes: qafvJhC3VKaH Fixes: swarm-Source-pointer-initialized-using-destination-linesize-in-vibrance_slice8-3c9b5e Found-by: zhang xingxing <[email protected]> Out of array read Replicated through UnModified FFmpeg with ASAN Signed-off-by: Michael Niedermayer <[email protected]> --- libavfilter/vf_vibrance.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libavfilter/vf_vibrance.c b/libavfilter/vf_vibrance.c index 295e5824120..273532e7500 100644 --- a/libavfilter/vf_vibrance.c +++ b/libavfilter/vf_vibrance.c @@ -85,9 +85,9 @@ static int vibrance_slice8(AVFilterContext *avctx, void *arg, int jobnr, int nb_ const ptrdiff_t bslinesize = in->linesize[1]; const ptrdiff_t rslinesize = in->linesize[2]; const ptrdiff_t aslinesize = in->linesize[3]; - const uint8_t *gsrc = in->data[0] + slice_start * glinesize; - const uint8_t *bsrc = in->data[1] + slice_start * blinesize; - const uint8_t *rsrc = in->data[2] + slice_start * rlinesize; + const uint8_t *gsrc = in->data[0] + slice_start * gslinesize; + const uint8_t *bsrc = in->data[1] + slice_start * bslinesize; + const uint8_t *rsrc = in->data[2] + slice_start * rslinesize; uint8_t *gptr = frame->data[0] + slice_start * glinesize; uint8_t *bptr = frame->data[1] + slice_start * blinesize; uint8_t *rptr = frame->data[2] + slice_start * rlinesize; -- 2.54.0 >From 2e63f41feaaddbb3e09cfa7a83ae27b2dc88bd98 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Fri, 6 Nov 2026 03:35:38 +0100 Subject: [PATCH 2/8] avfilter/avf_concat: split computing the padding silence from sending it Keep the silence to send per output and send it one frame per call. Signed-off-by: Michael Niedermayer <[email protected]> --- libavfilter/avf_concat.c | 65 +++++++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 24 deletions(-) diff --git a/libavfilter/avf_concat.c b/libavfilter/avf_concat.c index f6a1f758f52..09b3f091a6b 100644 --- a/libavfilter/avf_concat.c +++ b/libavfilter/avf_concat.c @@ -48,6 +48,11 @@ typedef struct ConcatContext { int64_t nb_frames; unsigned eof; } *in; + struct concat_silence { + int64_t pts; + int64_t nb_samples; + int64_t sent; + } *silence; } ConcatContext; #define OFFSET(x) offsetof(ConcatContext, x) @@ -244,16 +249,14 @@ static void find_next_delta_ts(AVFilterContext *ctx, int64_t *seg_delta) *seg_delta = pts; } -static int send_silence(AVFilterContext *ctx, unsigned in_no, unsigned out_no, - int64_t seg_delta) +static int queue_silence(AVFilterContext *ctx, unsigned in_no, unsigned out_no, + int64_t seg_delta) { ConcatContext *cat = ctx->priv; AVFilterLink *outlink = ctx->outputs[out_no]; - int64_t base_pts = cat->in[in_no].pts + cat->delta_ts - seg_delta; - int64_t nb_samples, sent = 0; - int frame_nb_samples, ret; + struct concat_silence *silence = &cat->silence[out_no]; + int64_t nb_samples; AVRational rate_tb = { 1, ctx->inputs[in_no]->sample_rate }; - AVFrame *buf; if (!rate_tb.den) return AVERROR_BUG; @@ -263,24 +266,31 @@ static int send_silence(AVFilterContext *ctx, unsigned in_no, unsigned out_no, return AVERROR_INVALIDDATA; nb_samples = av_rescale_q(seg_delta - cat->in[in_no].pts, outlink->time_base, rate_tb); - frame_nb_samples = FFMAX(9600, rate_tb.den / 5); /* arbitrary */ - while (nb_samples) { - frame_nb_samples = FFMIN(frame_nb_samples, nb_samples); - buf = ff_get_audio_buffer(outlink, frame_nb_samples); - if (!buf) - return AVERROR(ENOMEM); - av_samples_set_silence(buf->extended_data, 0, frame_nb_samples, - outlink->ch_layout.nb_channels, outlink->format); - buf->pts = base_pts + av_rescale_q(sent, rate_tb, outlink->time_base); - ret = ff_filter_frame(outlink, buf); - if (ret < 0) - return ret; - sent += frame_nb_samples; - nb_samples -= frame_nb_samples; - } + silence->pts = cat->in[in_no].pts + cat->delta_ts - seg_delta; + silence->nb_samples = nb_samples; + silence->sent = 0; return 0; } +static int send_silence(AVFilterContext *ctx, unsigned out_no) +{ + ConcatContext *cat = ctx->priv; + AVFilterLink *outlink = ctx->outputs[out_no]; + struct concat_silence *silence = &cat->silence[out_no]; + AVRational rate_tb = { 1, outlink->sample_rate }; + int frame_nb_samples = FFMIN(FFMAX(9600, rate_tb.den / 5), /* arbitrary */ + silence->nb_samples - silence->sent); + AVFrame *buf = ff_get_audio_buffer(outlink, frame_nb_samples); + + if (!buf) + return AVERROR(ENOMEM); + av_samples_set_silence(buf->extended_data, 0, frame_nb_samples, + outlink->ch_layout.nb_channels, outlink->format); + buf->pts = silence->pts + av_rescale_q(silence->sent, rate_tb, outlink->time_base); + silence->sent += frame_nb_samples; + return ff_filter_frame(outlink, buf); +} + static int flush_segment(AVFilterContext *ctx) { int ret; @@ -299,10 +309,15 @@ static int flush_segment(AVFilterContext *ctx) str = cat->nb_streams[AVMEDIA_TYPE_VIDEO]; str_max = str + cat->nb_streams[AVMEDIA_TYPE_AUDIO]; for (; str < str_max; str++) { - ret = send_silence(ctx, cat->cur_idx - ctx->nb_outputs + str, str, - seg_delta); + ret = queue_silence(ctx, cat->cur_idx - ctx->nb_outputs + str, str, + seg_delta); if (ret < 0) return ret; + while (cat->silence[str].sent < cat->silence[str].nb_samples) { + ret = send_silence(ctx, str); + if (ret < 0) + return ret; + } } } return 0; @@ -345,7 +360,8 @@ static av_cold int init(AVFilterContext *ctx) } cat->in = av_calloc(ctx->nb_inputs, sizeof(*cat->in)); - if (!cat->in) + cat->silence = av_calloc(ctx->nb_outputs, sizeof(*cat->silence)); + if (!cat->in || !cat->silence) return AVERROR(ENOMEM); cat->nb_in_active = ctx->nb_outputs; return 0; @@ -356,6 +372,7 @@ static av_cold void uninit(AVFilterContext *ctx) ConcatContext *cat = ctx->priv; av_freep(&cat->in); + av_freep(&cat->silence); } static int activate(AVFilterContext *ctx) -- 2.54.0 >From eeb67d8731082f1c0ebd73aa16141a198535a4f0 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Fri, 6 Nov 2026 04:20:53 +0100 Subject: [PATCH 3/8] avfilter/avf_concat: count the padding silence back from the segment end Signed-off-by: Michael Niedermayer <[email protected]> --- libavfilter/avf_concat.c | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/libavfilter/avf_concat.c b/libavfilter/avf_concat.c index 09b3f091a6b..00855d88885 100644 --- a/libavfilter/avf_concat.c +++ b/libavfilter/avf_concat.c @@ -49,9 +49,8 @@ typedef struct ConcatContext { unsigned eof; } *in; struct concat_silence { - int64_t pts; + int64_t end_pts; int64_t nb_samples; - int64_t sent; } *silence; } ConcatContext; @@ -255,7 +254,6 @@ static int queue_silence(AVFilterContext *ctx, unsigned in_no, unsigned out_no, ConcatContext *cat = ctx->priv; AVFilterLink *outlink = ctx->outputs[out_no]; struct concat_silence *silence = &cat->silence[out_no]; - int64_t nb_samples; AVRational rate_tb = { 1, ctx->inputs[in_no]->sample_rate }; if (!rate_tb.den) @@ -264,11 +262,9 @@ static int queue_silence(AVFilterContext *ctx, unsigned in_no, unsigned out_no, return AVERROR_INVALIDDATA; if (seg_delta < cat->in[in_no].pts) return AVERROR_INVALIDDATA; - nb_samples = av_rescale_q(seg_delta - cat->in[in_no].pts, - outlink->time_base, rate_tb); - silence->pts = cat->in[in_no].pts + cat->delta_ts - seg_delta; - silence->nb_samples = nb_samples; - silence->sent = 0; + silence->nb_samples = av_rescale_q(seg_delta - cat->in[in_no].pts, + outlink->time_base, rate_tb); + silence->end_pts = cat->delta_ts; return 0; } @@ -279,15 +275,16 @@ static int send_silence(AVFilterContext *ctx, unsigned out_no) struct concat_silence *silence = &cat->silence[out_no]; AVRational rate_tb = { 1, outlink->sample_rate }; int frame_nb_samples = FFMIN(FFMAX(9600, rate_tb.den / 5), /* arbitrary */ - silence->nb_samples - silence->sent); + silence->nb_samples); AVFrame *buf = ff_get_audio_buffer(outlink, frame_nb_samples); if (!buf) return AVERROR(ENOMEM); av_samples_set_silence(buf->extended_data, 0, frame_nb_samples, outlink->ch_layout.nb_channels, outlink->format); - buf->pts = silence->pts + av_rescale_q(silence->sent, rate_tb, outlink->time_base); - silence->sent += frame_nb_samples; + buf->pts = silence->end_pts - + av_rescale_q(silence->nb_samples, rate_tb, outlink->time_base); + silence->nb_samples -= frame_nb_samples; return ff_filter_frame(outlink, buf); } @@ -313,7 +310,7 @@ static int flush_segment(AVFilterContext *ctx) seg_delta); if (ret < 0) return ret; - while (cat->silence[str].sent < cat->silence[str].nb_samples) { + while (cat->silence[str].nb_samples) { ret = send_silence(ctx, str); if (ret < 0) return ret; -- 2.54.0 >From 35aca8f5a1882f23a59a008a3a5d1cdb8ebdf53b Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Fri, 6 Nov 2026 03:35:38 +0100 Subject: [PATCH 4/8] avfilter/avf_concat: send the padding silence one frame at a time Fixes: OOM Fixes: rYbksRctTGL7 Fixes: swarm-Unbounded-audio-silence-allocation-driven-by-crafted-PTS-c5e945 Found-by: zhang xingxing <[email protected]> OOM Replicated through UnModified FFmpeg with RSS monitoring Signed-off-by: Michael Niedermayer <[email protected]> --- libavfilter/avf_concat.c | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/libavfilter/avf_concat.c b/libavfilter/avf_concat.c index 00855d88885..63ffc70afd3 100644 --- a/libavfilter/avf_concat.c +++ b/libavfilter/avf_concat.c @@ -248,12 +248,21 @@ static void find_next_delta_ts(AVFilterContext *ctx, int64_t *seg_delta) *seg_delta = pts; } +static int silence_pending(AVFilterContext *ctx, unsigned out_no) +{ + ConcatContext *cat = ctx->priv; + + return cat->silence[out_no].nb_samples && + !ff_outlink_get_status(ctx->outputs[out_no]); +} + static int queue_silence(AVFilterContext *ctx, unsigned in_no, unsigned out_no, int64_t seg_delta) { ConcatContext *cat = ctx->priv; AVFilterLink *outlink = ctx->outputs[out_no]; struct concat_silence *silence = &cat->silence[out_no]; + int64_t nb_samples; AVRational rate_tb = { 1, ctx->inputs[in_no]->sample_rate }; if (!rate_tb.den) @@ -262,9 +271,12 @@ static int queue_silence(AVFilterContext *ctx, unsigned in_no, unsigned out_no, return AVERROR_INVALIDDATA; if (seg_delta < cat->in[in_no].pts) return AVERROR_INVALIDDATA; - silence->nb_samples = av_rescale_q(seg_delta - cat->in[in_no].pts, - outlink->time_base, rate_tb); - silence->end_pts = cat->delta_ts; + nb_samples = av_rescale_q(seg_delta - cat->in[in_no].pts, + outlink->time_base, rate_tb); + if (nb_samples < 0 || nb_samples > INT64_MAX - silence->nb_samples) + return AVERROR_INVALIDDATA; + silence->nb_samples += nb_samples; + silence->end_pts = cat->delta_ts; return 0; } @@ -310,11 +322,6 @@ static int flush_segment(AVFilterContext *ctx) seg_delta); if (ret < 0) return ret; - while (cat->silence[str].nb_samples) { - ret = send_silence(ctx, str); - if (ret < 0) - return ret; - } } } return 0; @@ -398,6 +405,8 @@ static int activate(AVFilterContext *ctx) /* Forward available frames */ if (cat->cur_idx < ctx->nb_inputs) { for (i = 0; i < ctx->nb_outputs; i++) { + if (silence_pending(ctx, i)) + continue; ret = ff_inlink_consume_frame(ctx->inputs[cat->cur_idx + i], &frame); if (ret < 0) return ret; @@ -413,6 +422,8 @@ static int activate(AVFilterContext *ctx) for (i = 0; i < ctx->nb_outputs; i++) { AVFilterLink *inlink = ctx->inputs[cat->cur_idx + i]; + if (silence_pending(ctx, i)) + continue; ret = ff_inlink_acknowledge_status(inlink, &status, &pts); /* TODO use pts */ if (ret > 0) { @@ -437,10 +448,15 @@ static int activate(AVFilterContext *ctx) ret = FFERROR_NOT_READY; for (i = 0; i < ctx->nb_outputs; i++) { if (ff_outlink_frame_wanted(ctx->outputs[i])) { + if (silence_pending(ctx, i)) + return send_silence(ctx, i); if (cat->in[cat->cur_idx + i].eof) { - for (j = 0; j < ctx->nb_outputs; j++) + for (j = 0; j < ctx->nb_outputs; j++) { + if (silence_pending(ctx, j)) + return send_silence(ctx, j); if (!cat->in[cat->cur_idx + j].eof) ff_inlink_request_frame(ctx->inputs[cat->cur_idx + j]); + } return 0; } else { ff_inlink_request_frame(ctx->inputs[cat->cur_idx + i]); -- 2.54.0 >From f5bcd20e79feed8a7bc9c90de0e0c77466976276 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Fri, 6 Nov 2026 03:19:21 +0100 Subject: [PATCH 5/8] avcodec/libjxldec: fail on Brotli compressed Exif boxes larger than 16 MiB Regression since 8132ee046d. Fixes: OOM Fixes: OBQVvFEd6jIZ Fixes: swarm-Unbounded-EXIF-buffer-doubling-causes-OOM-fd8b90 Found-by: zhang xingxing <[email protected]> OOM Replicated through UnModified FFmpeg with RSS monitoring Signed-off-by: Michael Niedermayer <[email protected]> --- libavcodec/libjxldec.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/libavcodec/libjxldec.c b/libavcodec/libjxldec.c index a2f125998e9..6eb134416dd 100644 --- a/libavcodec/libjxldec.c +++ b/libavcodec/libjxldec.c @@ -45,6 +45,8 @@ #include <jxl/thread_parallel_runner.h> #include "libjxl.h" +#define EXIF_MAX_SIZE (16 << 20) + typedef struct LibJxlDecodeContext { void *runner; JxlDecoder *decoder; @@ -629,6 +631,15 @@ success: size_t remainder = JxlDecoderReleaseBoxBuffer(ctx->decoder); ctx->exif_pos = ctx->exif->size - remainder; size_t new_size = ctx->exif->size << 1; + JxlBoxType type; + if (new_size > EXIF_MAX_SIZE && + (JxlDecoderGetBoxType(ctx->decoder, type, JXL_FALSE) != JXL_DEC_SUCCESS || + AV_RL32(type) == MKTAG('b','r','o','b'))) { + av_log(avctx, AV_LOG_ERROR, "Compressed Exif box larger than %d bytes\n", EXIF_MAX_SIZE); + av_buffer_unref(&ctx->exif); + ctx->exif_box = 0; + return AVERROR_INVALIDDATA; + } ret = av_buffer_realloc(&ctx->exif, new_size); if (ret < 0) return AVERROR(ENOMEM); -- 2.54.0 >From a8592954469f519a828e14794dac1cf01bce1de7 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Tue, 3 Nov 2026 16:13:38 +0100 Subject: [PATCH 6/8] avfilter/dnn_backend_tf: free all output tensors of a request This is not a security issue. Fixes: hYQpMVEPvybk Fixes: swarm-tf_free_request-always-frees-only-one-output-tensor-21656a Found-by: zhang xingxing <[email protected]> Memory leak Replicated through UnModified FFmpeg with LSan Signed-off-by: Michael Niedermayer <[email protected]> --- libavfilter/dnn/dnn_backend_tf.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libavfilter/dnn/dnn_backend_tf.c b/libavfilter/dnn/dnn_backend_tf.c index f722e8bf8df..51dd88ce1c9 100644 --- a/libavfilter/dnn/dnn_backend_tf.c +++ b/libavfilter/dnn/dnn_backend_tf.c @@ -54,6 +54,7 @@ typedef struct TFModel { typedef struct TFInferRequest { TF_Output *tf_outputs; TF_Tensor **output_tensors; + uint32_t nb_output; TF_Output *tf_input; TF_Tensor *input_tensor; } TFInferRequest; @@ -100,8 +101,7 @@ static void tf_free_request(TFInferRequest *request) av_freep(&request->tf_input); av_freep(&request->tf_outputs); if (request->output_tensors) { - int nb_output = sizeof(*request->output_tensors)/sizeof(request->output_tensors[0]); - for (uint32_t i = 0; i < nb_output; ++i) { + for (uint32_t i = 0; i < request->nb_output; ++i) { if (request->output_tensors[i]) { TF_DeleteTensor(request->output_tensors[i]); request->output_tensors[i] = NULL; @@ -673,6 +673,7 @@ static int fill_model_input_tf(TFModel *tf_model, TFRequestItem *request) { ret = AVERROR(ENOMEM); goto err; } + infer_request->nb_output = task->nb_output; for (int i = 0; i < task->nb_output; ++i) { infer_request->output_tensors[i] = NULL; -- 2.54.0 >From ba8efc8856eaf26e00ba569673e68c176b06bcdb Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Tue, 3 Nov 2026 20:41:10 +0100 Subject: [PATCH 7/8] avcodec/libjxldec: return EOF after reporting a truncated codestream Regression since 2c8835dd72. Fixes: infinite loop Fixes: k8CycfOvfx1i Found during triage of the security report OBQVvFEd6jIZ Infinite loop Replicated through UnModified FFmpeg with a timeout Signed-off-by: Michael Niedermayer <[email protected]> --- libavcodec/libjxldec.c | 1 + 1 file changed, 1 insertion(+) diff --git a/libavcodec/libjxldec.c b/libavcodec/libjxldec.c index 6eb134416dd..693c2b48e11 100644 --- a/libavcodec/libjxldec.c +++ b/libavcodec/libjxldec.c @@ -450,6 +450,7 @@ static int libjxl_receive_frame(AVCodecContext *avctx, AVFrame *frame) /* jret set by the last iteration of the loop */ if (ctx->jret == JXL_DEC_NEED_MORE_INPUT && !ctx->frame_complete) { av_log(avctx, AV_LOG_ERROR, "Unexpected end of JXL codestream\n"); + ctx->jret = JXL_DEC_ERROR; return AVERROR_INVALIDDATA; } else if (ctx->frame_complete) { ctx->jret = JXL_DEC_SUCCESS; -- 2.54.0 >From 7f95427bbac833a2aaf061669c70ef346d8067b7 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Tue, 3 Nov 2026 20:42:25 +0100 Subject: [PATCH 8/8] avutil/hash: terminate the hex string when size is 1 Found during triage of the security report qG4gdnzp4yAQ Unterminated string Replicated through API with ASAN Signed-off-by: Michael Niedermayer <[email protected]> --- libavutil/hash.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libavutil/hash.c b/libavutil/hash.c index fbc24194dea..10f055aa401 100644 --- a/libavutil/hash.c +++ b/libavutil/hash.c @@ -228,7 +228,7 @@ void av_hash_final_hex(struct AVHashContext *ctx, uint8_t *dst, int size) unsigned rsize = av_hash_get_size(ctx), i; av_hash_final(ctx, buf); - for (i = 0; i < FFMIN(rsize, size / 2); i++) + for (i = 0; i < FFMIN(rsize, size - size / 2); i++) snprintf(dst + i * 2, size - i * 2, "%02x", buf[i]); } -- 2.54.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
