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]

Reply via email to