PR #23128 opened by michaelni URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23128 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23128.patch
>From c74ed60525ad6e0da6fcbc48d2e40f35f3983edb Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Sun, 17 May 2026 13:45:38 +0200 Subject: [PATCH 1/3] avfilter/vf_drawtext: don't double-free glyph that has been cached in tree Reproducer: ffmpeg -f lavfi -i color=s=640x120:d=1 \ -vf "drawtext=fontfile=/usr/share/fonts/truetype/noto/NotoColorEmoji.ttf:\ text='FFmpeg':fontsize=109:fontcolor=white:x=20:y=35" \ -frames:v 1 out.png Signed-off-by: Michael Niedermayer <[email protected]> --- libavfilter/vf_drawtext.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/libavfilter/vf_drawtext.c b/libavfilter/vf_drawtext.c index 005e150de7..d60f412635 100644 --- a/libavfilter/vf_drawtext.c +++ b/libavfilter/vf_drawtext.c @@ -735,11 +735,13 @@ static int load_glyph(AVFilterContext *ctx, Glyph **glyph_ptr, uint32_t code, in FT_Vector shift; struct AVTreeNode *node = NULL; int ret = 0; + int cached = 0; /* get glyph */ dummy.code = code; dummy.fontsize = s->fontsize; glyph = av_tree_find(s->glyphs, &dummy, glyph_cmp, NULL); + cached = !!glyph; if (!glyph) { if (FT_Load_Glyph(s->face, code, s->ft_load_flags)) { return AVERROR(EINVAL); @@ -771,6 +773,7 @@ static int load_glyph(AVFilterContext *ctx, Glyph **glyph_ptr, uint32_t code, in goto error; } av_tree_insert(&s->glyphs, glyph, glyph_cmp, &node); + cached = 1; } else { if (s->borderw && !glyph->border_glyph) { glyph->border_glyph = glyph->glyph; @@ -816,10 +819,11 @@ static int load_glyph(AVFilterContext *ctx, Glyph **glyph_ptr, uint32_t code, in return 0; error: - if (glyph && glyph->glyph) - FT_Done_Glyph(glyph->glyph); - - av_freep(&glyph); + if (!cached) { + if (glyph && glyph->glyph) + FT_Done_Glyph(glyph->glyph); + av_freep(&glyph); + } av_freep(&node); return ret; } -- 2.52.0 >From 104b9aaeebed33dfb311273f94b34be4de86cc2d Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Sun, 17 May 2026 13:49:39 +0200 Subject: [PATCH 2/3] avfilter/vf_drawtext: avoid double-free of aliased FT_Glyph in glyph_enu_free For glyphs whose source is already in bitmap form (color emoji fonts such as NotoColorEmoji.ttf), FT_Glyph_To_Bitmap(..., destroy=0) returns the input pointer unchanged. The result is that glyph->bglyph[idx] aliases glyph->glyph (and analogously border_bglyph[t] may alias border_glyph). glyph_enu_free then called FT_Done_Glyph on both, double-freeing the underlying object. Signed-off-by: Michael Niedermayer <[email protected]> --- libavfilter/vf_drawtext.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/libavfilter/vf_drawtext.c b/libavfilter/vf_drawtext.c index d60f412635..f55d995988 100644 --- a/libavfilter/vf_drawtext.c +++ b/libavfilter/vf_drawtext.c @@ -1104,16 +1104,17 @@ static int glyph_enu_free(void *opaque, void *elem) { Glyph *glyph = elem; - FT_Done_Glyph(glyph->glyph); - FT_Done_Glyph(glyph->border_glyph); for (int t = 0; t < 16; ++t) { - if (glyph->bglyph[t] != NULL) { - FT_Done_Glyph((FT_Glyph)glyph->bglyph[t]); - } - if (glyph->border_bglyph[t] != NULL) { - FT_Done_Glyph((FT_Glyph)glyph->border_bglyph[t]); - } + FT_Glyph bg = (FT_Glyph)glyph->bglyph[t]; + FT_Glyph bbg = (FT_Glyph)glyph->border_bglyph[t]; + if (bg && bg != glyph->glyph && bg != glyph->border_glyph) + FT_Done_Glyph(bg); + if (bbg && bbg != glyph->glyph && bbg != glyph->border_glyph) + FT_Done_Glyph(bbg); } + if (glyph->border_glyph && glyph->border_glyph != glyph->glyph) + FT_Done_Glyph(glyph->border_glyph); + FT_Done_Glyph(glyph->glyph); av_free(elem); return 0; } -- 2.52.0 >From fdbb70c4d8823c6baa716b371b41ba1ae0056f1a Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Sun, 17 May 2026 15:14:35 +0200 Subject: [PATCH 3/3] avfilter/vf_drawtext: plug error-path leaks in measure_text/draw_text Signed-off-by: Michael Niedermayer <[email protected]> --- libavfilter/vf_drawtext.c | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/libavfilter/vf_drawtext.c b/libavfilter/vf_drawtext.c index f55d995988..f8a7fbf01d 100644 --- a/libavfilter/vf_drawtext.c +++ b/libavfilter/vf_drawtext.c @@ -1556,6 +1556,15 @@ continue_on_failed2: done: av_free(textdup); + if (ret < 0) { + if (s->lines) { + for (int l = 0; l < s->line_count; ++l) + hb_destroy(&s->lines[l].hb_data); + } + av_freep(&s->lines); + av_freep(&s->tab_clusters); + s->line_count = 0; + } return ret; } @@ -1746,7 +1755,7 @@ static int draw_text(AVFilterContext *ctx, AVFrame *frame) ret = load_glyph(ctx, &glyph, hb->glyph_info[t].codepoint, shift_x64, shift_y64); if (ret != 0) { - return ret; + goto fail; } g_info->code = hb->glyph_info[t].codepoint; g_info->x = (x64 + true_x) >> 6; @@ -1808,23 +1817,25 @@ static int draw_text(AVFilterContext *ctx, AVFrame *frame) if (s->shadowx || s->shadowy) { if ((ret = draw_glyphs(ctx, frame, &shadowcolor, &metrics, s->shadowx, s->shadowy, s->borderw)) < 0) { - return ret; + goto fail; } } if (s->borderw) { if ((ret = draw_glyphs(ctx, frame, &bordercolor, &metrics, 0, 0, s->borderw)) < 0) { - return ret; + goto fail; } } if ((ret = draw_glyphs(ctx, frame, &fontcolor, &metrics, 0, 0, 0)) < 0) { - return ret; + goto fail; } } + ret = 0; +fail: // FREE data structures for (int l = 0; l < s->line_count; ++l) { TextLine *line = &s->lines[l]; @@ -1833,8 +1844,9 @@ static int draw_text(AVFilterContext *ctx, AVFrame *frame) } av_freep(&s->lines); av_freep(&s->tab_clusters); + s->line_count = 0; - return 0; + return ret; } static int filter_frame(AVFilterLink *inlink, AVFrame *frame) -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
