Greetings,

The freeNativeResources function in freetypeScaler.c frees a pointer obtained from FreeType internals:

    stream = scalerInfo->face->stream;

    FT_Done_Face(scalerInfo->face);

...snip...

   if (stream != NULL) {
        free(stream);
   }

This is done in order to free a FT_StreamRec that Java allocates in some cases when initialising the native scaler. However, the current approach is wrong because:

- FT_Done_Face will itself also free the face->stream pointer, if
  that memory was allocated by FreeType itself instead of by Java.
- FT_FaceRec.stream is explicitly noted to be a private field in
  FreeType documentation.

With the way Java uses FreeType, FT ends up allocating the FT_StreamRec structure in Java's Type 1 font case. As a result we've been observing JVM crashes due to a double free when:
- a java.awt.Font is constructed from Type 1 font data
- it becomes garbage
- the associated FreetypeFontScaler is disposed

The attached FontDisposeTest.java can demonstrate the crash. Pass the path to a Type 1 font file as argument and it will load the font and force native scaler disposal. If memory allocation debugging is not active by default, it can be forced on by setting an environment variable 'MALLOC_CHECK_=3' (glibc, for Windows see PageHeap). A Type 1 font file can be found from Ghostscript (ftp://ftp.gnu.org/gnu/ghostscript/gnu-gs-fonts-other-6.0.tar.gz)


FreeType records whether the pointer in FT_FaceRec.stream was allocated externally or by itself in the FT_FaceRec.face_flags field. However the documentation for FT_FACE_FLAG_EXTERNAL_STREAM says

Used internally by FreeType to indicate that a face's stream was provided by 
the client application and should not be destroyed when FT_Done_Face is called. 
Don't read or test this flag.

Therefore Java should maintain it's own copy of the stream pointer so it can know with certainty what memory it needs to free, if any.

Attached are patches to fix the bug in jdk8u and jdk9. I've added a field `faceStream` to the FTScalerInfo struct to keep track of the Java-allocated stream.



PS. if this is not the correct mailing list, or a valid way of submitting a patch, could you point me to the correct one?


 -- Heikki Aitakangas
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.font.FontRenderContext;
import java.awt.image.BufferedImage;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class FontDisposeTest
{
  public static void main(String[] args) throws Throwable
  {
    // The bug only happens with Type 1 fonts. The Ghostscript font files
    // should be commonly available. From distro pacakge or
    //  ftp://ftp.gnu.org/gnu/ghostscript/gnu-gs-fonts-other-6.0.tar.gz
    String path = args[0];

    // Load
    System.out.printf("Loading font from '%s'... ",path);
    InputStream stream = new FileInputStream(path);
    Font font = Font.createFont(Font.TYPE1_FONT,stream);
    System.out.printf(" done.\n");

    // Ensure native bits have been generated
    BufferedImage img = new BufferedImage(100,100,BufferedImage.TYPE_INT_ARGB);
    Graphics2D g2d = img.createGraphics();
    FontRenderContext frc = g2d.getFontRenderContext();

    font.getLineMetrics("derp",frc);
    System.out.printf("Font native scaler initialised.\n");

    // Force disposal - I have not yet figured out a way to get it to happen
    // naturally in a test case context. Just waiting and/or calling
    // System.gc() is not sufficient.
    Field font2DHandleField = Font.class.getDeclaredField("font2DHandle");
    font2DHandleField.setAccessible(true);
    sun.font.Font2DHandle font2DHandle =
      (sun.font.Font2DHandle)font2DHandleField.get(font);

    sun.font.Font2D font2D = font2DHandle.font2D;
    sun.font.Type1Font type1Font = (sun.font.Type1Font)font2D;

    Method getScalerMethod =
      sun.font.Type1Font.class.getDeclaredMethod("getScaler");
    getScalerMethod.setAccessible(true);
    sun.font.FontScaler scaler =
      (sun.font.FontScaler)getScalerMethod.invoke(type1Font);

    System.out.println("Forcing font native scaler disposal.\n");
    scaler.dispose();
    System.out.println("\nIf nothing happened, try again with environment variable 'MALLOC_CHECK_=3'.\n");
  }
}
# HG changeset patch
# User Heikki Aitakangas <heikki.aitakan...@documill.com>
# Date 1438693351 -10800
#      Tue Aug 04 16:02:31 2015 +0300
# Node ID d35700befb98097f7457e066844e662edd1825f6
# Parent  be5faa9c77042f202106c18f4e8ea211137b4a3b
Fixed a double free bug in FreeType native scaler.

diff --git a/src/share/native/sun/font/freetypeScaler.c b/src/share/native/sun/font/freetypeScaler.c
--- a/src/share/native/sun/font/freetypeScaler.c
+++ b/src/share/native/sun/font/freetypeScaler.c
@@ -60,6 +60,7 @@
     JNIEnv* env;
     FT_Library library;
     FT_Face face;
+    FT_Stream faceStream;
     jobject font2D;
     jobject directBuffer;
 
@@ -107,15 +108,10 @@
     if (scalerInfo == NULL)
         return;
 
-    //apparently Done_Face will only close the stream
-    // but will not relase the memory of stream structure.
-    // We need to free it explicitly to avoid leak.
-    //Direct access to the stream field might be not ideal solution as
-    // it is considred to be "private".
-    //Alternatively we could have stored pointer to the structure
-    // in the scalerInfo but this will increase size of the structure
-    // for no good reason
-    stream = scalerInfo->face->stream;
+    // FT_Done_Face always closes the stream, but only frees the memory
+    // of the data structure if it was internally allocated by FT.
+    // We hold on to a pointer to the stream structure if we provide it
+    // ourselves, so that we can free it here.
 
     FT_Done_Face(scalerInfo->face);
     FT_Done_FreeType(scalerInfo->library);
@@ -128,8 +124,8 @@
         free(scalerInfo->fontData);
     }
 
-   if (stream != NULL) {
-        free(stream);
+   if (scalerInfo->faceStream != NULL) {
+        free(scalerInfo->faceStream);
    }
 
     free(scalerInfo);
@@ -302,6 +298,10 @@
                                          &ft_open_args,
                                          indexInCollection,
                                          &scalerInfo->face);
+
+                    if (!error) {
+                        scalerInfo->faceStream = ftstream;
+                    }
                 }
                 if (error || scalerInfo->directBuffer == NULL) {
                     free(ftstream);
# HG changeset patch
# User Heikki Aitakangas <heikki.aitakan...@documill.com>
# Date 1438697194 -10800
#      Tue Aug 04 17:06:34 2015 +0300
# Node ID 95c9d9e47bd44bba1f52eee9d91b972dfa632441
# Parent  ab2f946581d1f030c1324ebd5f5c1fb56447039b
Fixed a double free bug in FreeType native scaler.

diff --git a/src/java.desktop/share/native/libfontmanager/freetypeScaler.c b/src/java.desktop/share/native/libfontmanager/freetypeScaler.c
--- a/src/java.desktop/share/native/libfontmanager/freetypeScaler.c
+++ b/src/java.desktop/share/native/libfontmanager/freetypeScaler.c
@@ -60,6 +60,7 @@
     JNIEnv* env;
     FT_Library library;
     FT_Face face;
+    FT_Stream faceStream;
     jobject font2D;
     jobject directBuffer;
 
@@ -107,15 +108,10 @@
     if (scalerInfo == NULL)
         return;
 
-    //apparently Done_Face will only close the stream
-    // but will not relase the memory of stream structure.
-    // We need to free it explicitly to avoid leak.
-    //Direct access to the stream field might be not ideal solution as
-    // it is considred to be "private".
-    //Alternatively we could have stored pointer to the structure
-    // in the scalerInfo but this will increase size of the structure
-    // for no good reason
-    stream = scalerInfo->face->stream;
+    // FT_Done_Face always closes the stream, but only frees the memory
+    // of the data structure if it was internally allocated by FT.
+    // We hold on to a pointer to the stream structure if we provide it
+    // ourselves, so that we can free it here.
 
     FT_Done_Face(scalerInfo->face);
     FT_Done_FreeType(scalerInfo->library);
@@ -128,8 +124,8 @@
         free(scalerInfo->fontData);
     }
 
-   if (stream != NULL) {
-        free(stream);
+   if (scalerInfo->faceStream != NULL) {
+        free(scalerInfo->faceStream);
    }
 
     free(scalerInfo);
@@ -302,6 +298,10 @@
                                          &ft_open_args,
                                          indexInCollection,
                                          &scalerInfo->face);
+
+                    if (!error) {
+                        scalerInfo->faceStream = ftstream;
+                    }
                 }
                 if (error || scalerInfo->directBuffer == NULL) {
                     free(ftstream);

Reply via email to