CVE-2019-6978 (published 2019-01-28T08:29Z)

The GD Graphics Library (aka LibGD) 2.2.5 has a double free in the gdImage*Ptr() functions in gd_gif_out.c, gd_jpeg.c, and gd_wbmp.c. NOTE: PHP is unaffected.

Severity: HIGH
CVSS v2 base score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 9.8, CRITICAL (CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-415
cwe_name: Double Free
description: The product calls free() twice on the same memory address, potentially leading to modification of unexpected memory locations.
extended_description: When a program calls free() twice with the same argument, the program's memory management data structures become corrupted. This corruption can cause the program to crash or, in some circumstances, cause two later calls to malloc() to return the same pointer. If malloc() returns the same value twice and the program later gives the attacker control over the data that is written into this doubly-allocated memory, the program becomes vulnerable to a buffer overflow attack.
         

Fix information:
repo_url: https://github.com/php/php-src
commit_details: repo_url: https://github.com/php/php-src
author: Christoph M. Becker
author_date: 2019-01-19 10:35:39+01:00
msg: Sync with upstream

Even though libgd/libgd#492 is not a relevant bug fix for PHP, since
the binding doesn't use the `gdImage*Ptr()` functions at all, we're
porting the fix to stay in sync here.
repository: repo_url: https://github.com/php/php-src
repo_name: php/php-src
description: The PHP Interpreter
repo_language: C
owner: php
file_changes: filename: gd_gif_out.c
change_type: ModificationType.MODIFY
diff: @@ -97,12 +97,18 @@ static void cl_hash (register count_int chsize, GifCtx *ctx);
 static void char_init (GifCtx *ctx);
 static void char_out (int c, GifCtx *ctx);
 static void flush_char (GifCtx *ctx);
+
+static int _gdImageGifCtx(gdImagePtr im, gdIOCtxPtr out);
+
 void * gdImageGifPtr (gdImagePtr im, int *size)
 {
   void *rv;
   gdIOCtx *out = gdNewDynamicCtx (2048, NULL);
-  gdImageGifCtx (im, out);
-  rv = gdDPExtractData (out, size);
+	if (!_gdImageGifCtx(im, out)) {
+		rv = gdDPExtractData(out, size);
+	} else {
+		rv = NULL;
+	}
   out->gd_free (out);
   return rv;
 }
@@ -115,6 +121,12 @@ void gdImageGif (gdImagePtr im, FILE * outFile)
 }
 
 void gdImageGifCtx(gdImagePtr im, gdIOCtxPtr out)
+{
+	_gdImageGifCtx(im, out);
+}
+
+/* returns 0 on success, 1 on failure */
+static int _gdImageGifCtx(gdImagePtr im, gdIOCtxPtr out)
 {
 	gdImagePtr pim = 0, tim = im;
 	int interlace, BitsPerPixel;
@@ -125,7 +137,7 @@ void gdImageGifCtx(gdImagePtr im, gdIOCtxPtr out)
 			based temporary image. */
 		pim = gdImageCreatePaletteFromTrueColor(im, 1, 256);
 		if (!pim) {
-			return;
+			return 1;
 		}
 		tim = pim;
 	}
@@ -138,6 +150,8 @@ void gdImageGifCtx(gdImagePtr im, gdIOCtxPtr out)
 		/* Destroy palette based temporary image. */
 		gdImageDestroy(	pim);
 	}
+
+    return 0;
 }
 
 static int

programming_language: C
filename: gd_jpeg.c
change_type: ModificationType.MODIFY
diff: @@ -132,6 +132,7 @@ const char * gdJpegGetVersionString()
 	}
 }
 
+static int _gdImageJpegCtx(gdImagePtr im, gdIOCtx *outfile, int quality);
 
 /*
  * Write IM to OUTFILE as a JFIF-formatted JPEG image, using quality
@@ -153,8 +154,11 @@ void *gdImageJpegPtr (gdImagePtr im, int *size, int quality)
 {
 	void *rv;
 	gdIOCtx *out = gdNewDynamicCtx (2048, NULL);
-	gdImageJpegCtx (im, out, quality);
-	rv = gdDPExtractData (out, size);
+	if (!_gdImageJpegCtx(im, out, quality)) {
+		rv = gdDPExtractData(out, size);
+	} else {
+		rv = NULL;
+	}
 	out->gd_free (out);
 
 	return rv;
@@ -163,6 +167,12 @@ void *gdImageJpegPtr (gdImagePtr im, int *size, int quality)
 void jpeg_gdIOCtx_dest (j_compress_ptr cinfo, gdIOCtx * outfile);
 
 void gdImageJpegCtx (gdImagePtr im, gdIOCtx * outfile, int quality)
+{
+	_gdImageJpegCtx(im, outfile, quality);
+}
+
+/* returns 0 on success, 1 on failure */
+static int _gdImageJpegCtx(gdImagePtr im, gdIOCtx *outfile, int quality)
 {
 	struct jpeg_compress_struct cinfo;
 	struct jpeg_error_mgr jerr;
@@ -184,7 +194,7 @@ void gdImageJpegCtx (gdImagePtr im, gdIOCtx * outfile, int quality)
 		if (row) {
 			gdFree (row);
 		}
-		return;
+		return 1;
 	}
 
 	cinfo.err->error_exit = fatal_jpeg_error;
@@ -277,6 +287,7 @@ void gdImageJpegCtx (gdImagePtr im, gdIOCtx * outfile, int quality)
 	jpeg_finish_compress (&cinfo);
 	jpeg_destroy_compress (&cinfo);
 	gdFree (row);
+	return 0;
 }
 
 gdImagePtr gdImageCreateFromJpeg (FILE * inFile)

programming_language: C
filename: gd_wbmp.c
change_type: ModificationType.MODIFY
diff: @@ -82,6 +82,7 @@ int gd_getin (void *in)
 	return (gdGetC((gdIOCtx *) in));
 }
 
+static int _gdImageWBMPCtx(gdImagePtr image, int fg, gdIOCtx *out);
 
 /*      gdImageWBMPCtx
    **  --------------
@@ -93,6 +94,12 @@ int gd_getin (void *in)
    **  out:    the stream where to write
  */
 void gdImageWBMPCtx (gdImagePtr image, int fg, gdIOCtx * out)
+{
+	_gdImageWBMPCtx(image, fg, out);
+}
+
+/* returns 0 on success, 1 on failure */
+static int _gdImageWBMPCtx(gdImagePtr image, int fg, gdIOCtx *out)
 {
 	int x, y, pos;
 	Wbmp *wbmp;
@@ -100,7 +107,7 @@ void gdImageWBMPCtx (gdImagePtr image, int fg, gdIOCtx * out)
 	/* create the WBMP */
 	if ((wbmp = createwbmp (gdImageSX (image), gdImageSY (image), WBMP_WHITE)) == NULL) {
 		gd_error("Could not create WBMP");
-		return;
+		return 1;
 	}
 
 	/* fill up the WBMP structure */
@@ -116,7 +123,9 @@ void gdImageWBMPCtx (gdImagePtr image, int fg, gdIOCtx * out)
 
 	/* write the WBMP to a gd file descriptor */
 	if (writewbmp (wbmp, &gd_putout, out)) {
+		freewbmp(wbmp);
 		gd_error("Could not save WBMP");
+		return 1;
 	}
 	/* des submitted this bugfix: gdFree the memory. */
 	freewbmp(wbmp);
@@ -204,8 +213,11 @@ void * gdImageWBMPPtr (gdImagePtr im, int *size, int fg)
 {
 	void *rv;
 	gdIOCtx *out = gdNewDynamicCtx(2048, NULL);
-	gdImageWBMPCtx(im, fg, out);
-	rv = gdDPExtractData(out, size);
+	if (!_gdImageWBMPCtx(im, fg, out)) {
+		rv = gdDPExtractData(out, size);
+	} else {
+		rv = NULL;
+	}
 	out->gd_free(out);
 
 	return rv;

programming_language: C
repo_url: https://github.com/libgd/libgd
commit_details: repo_url: https://github.com/libgd/libgd
author: Christoph M. Becker
author_date: 2019-01-17 11:54:55+01:00
msg: Fix #492: Potential double-free in gdImage*Ptr()

Whenever `gdImage*Ptr()` calls `gdImage*Ctx()` and the latter fails, we
must not call `gdDPExtractData()`; otherwise a double-free would
happen.  Since `gdImage*Ctx()` are void functions, and we can't change
that for BC reasons, we're introducing static helpers which are used
internally.

We're adding a regression test for `gdImageJpegPtr()`, but not for
`gdImageGifPtr()` and `gdImageWbmpPtr()` since we don't know how to
trigger failure of the respective `gdImage*Ctx()` calls.

This potential security issue has been reported by Solmaz Salimi (aka.
Rooney).
repository: repo_url: https://github.com/libgd/libgd
repo_name: libgd/libgd
description: GD Graphics Library
repo_language: C
owner: libgd
file_changes: filename: gd_gif_out.c
change_type: ModificationType.MODIFY
diff: @@ -99,6 +99,7 @@ static void char_init(GifCtx *ctx);
 static void char_out(int c, GifCtx *ctx);
 static void flush_char(GifCtx *ctx);
 
+static int _gdImageGifCtx(gdImagePtr im, gdIOCtxPtr out);
 
 
 
@@ -131,8 +132,11 @@ BGD_DECLARE(void *) gdImageGifPtr(gdImagePtr im, int *size)
 	void *rv;
 	gdIOCtx *out = gdNewDynamicCtx(2048, NULL);
 	if (out == NULL) return NULL;
-	gdImageGifCtx(im, out);
-	rv = gdDPExtractData(out, size);
+	if (!_gdImageGifCtx(im, out)) {
+		rv = gdDPExtractData(out, size);
+	} else {
+		rv = NULL;
+	}
 	out->gd_free(out);
 	return rv;
 }
@@ -220,6 +224,12 @@ BGD_DECLARE(void) gdImageGif(gdImagePtr im, FILE *outFile)
 
 */
 BGD_DECLARE(void) gdImageGifCtx(gdImagePtr im, gdIOCtxPtr out)
+{
+	_gdImageGifCtx(im, out);
+}
+
+/* returns 0 on success, 1 on failure */
+static int _gdImageGifCtx(gdImagePtr im, gdIOCtxPtr out)
 {
 	gdImagePtr pim = 0, tim = im;
 	int interlace, BitsPerPixel;
@@ -231,7 +241,7 @@ BGD_DECLARE(void) gdImageGifCtx(gdImagePtr im, gdIOCtxPtr out)
 		based temporary image. */
 		pim = gdImageCreatePaletteFromTrueColor(im, 1, 256);
 		if(!pim) {
-			return;
+			return 1;
 		}
 		tim = pim;
 	}
@@ -247,6 +257,8 @@ BGD_DECLARE(void) gdImageGifCtx(gdImagePtr im, gdIOCtxPtr out)
 		/* Destroy palette based temporary image. */
 		gdImageDestroy(	pim);
 	}
+
+	return 0;
 }
 
 

programming_language: C
filename: gd_jpeg.c
change_type: ModificationType.MODIFY
diff: @@ -117,6 +117,8 @@ static void fatal_jpeg_error(j_common_ptr cinfo)
 	exit(99);
 }
 
+static int _gdImageJpegCtx(gdImagePtr im, gdIOCtx *outfile, int quality);
+
 /*
  * Write IM to OUTFILE as a JFIF-formatted JPEG image, using quality
  * QUALITY.  If QUALITY is in the range 0-100, increasing values
@@ -231,8 +233,11 @@ BGD_DECLARE(void *) gdImageJpegPtr(gdImagePtr im, int *size, int quality)
 	void *rv;
 	gdIOCtx *out = gdNewDynamicCtx(2048, NULL);
 	if (out == NULL) return NULL;
-	gdImageJpegCtx(im, out, quality);
-	rv = gdDPExtractData(out, size);
+	if (!_gdImageJpegCtx(im, out, quality)) {
+		rv = gdDPExtractData(out, size);
+	} else {
+		rv = NULL;
+	}
 	out->gd_free(out);
 	return rv;
 }
@@ -253,6 +258,12 @@ void jpeg_gdIOCtx_dest(j_compress_ptr cinfo, gdIOCtx *outfile);
 
 */
 BGD_DECLARE(void) gdImageJpegCtx(gdImagePtr im, gdIOCtx *outfile, int quality)
+{
+	_gdImageJpegCtx(im, outfile, quality);
+}
+
+/* returns 0 on success, 1 on failure */
+static int _gdImageJpegCtx(gdImagePtr im, gdIOCtx *outfile, int quality)
 {
 	struct jpeg_compress_struct cinfo;
 	struct jpeg_error_mgr jerr;
@@ -287,7 +298,7 @@ BGD_DECLARE(void) gdImageJpegCtx(gdImagePtr im, gdIOCtx *outfile, int quality)
 		if(row) {
 			gdFree(row);
 		}
-		return;
+		return 1;
 	}
 
 	cinfo.err->emit_message = jpeg_emit_message;
@@ -328,7 +339,7 @@ BGD_DECLARE(void) gdImageJpegCtx(gdImagePtr im, gdIOCtx *outfile, int quality)
 	if(row == 0) {
 		gd_error("gd-jpeg: error: unable to allocate JPEG row structure: gdCalloc returns NULL\n");
 		jpeg_destroy_compress(&cinfo);
-		return;
+		return 1;
 	}
 
 	rowptr[0] = row;
@@ -405,6 +416,7 @@ BGD_DECLARE(void) gdImageJpegCtx(gdImagePtr im, gdIOCtx *outfile, int quality)
 	jpeg_finish_compress(&cinfo);
 	jpeg_destroy_compress(&cinfo);
 	gdFree(row);
+	return 0;
 }
 
 

programming_language: C
filename: gd_wbmp.c
change_type: ModificationType.MODIFY
diff: @@ -88,6 +88,8 @@ int gd_getin(void *in)
 	return (gdGetC((gdIOCtx *)in));
 }
 
+static int _gdImageWBMPCtx(gdImagePtr image, int fg, gdIOCtx *out);
+
 /*
 	Function: gdImageWBMPCtx
 
@@ -100,6 +102,12 @@ int gd_getin(void *in)
 		out   - the stream where to write
 */
 BGD_DECLARE(void) gdImageWBMPCtx(gdImagePtr image, int fg, gdIOCtx *out)
+{
+	_gdImageWBMPCtx(image, fg, out);
+}
+
+/* returns 0 on success, 1 on failure */
+static int _gdImageWBMPCtx(gdImagePtr image, int fg, gdIOCtx *out)
 {
 	int x, y, pos;
 	Wbmp *wbmp;
@@ -107,7 +115,7 @@ BGD_DECLARE(void) gdImageWBMPCtx(gdImagePtr image, int fg, gdIOCtx *out)
 	/* create the WBMP */
 	if((wbmp = createwbmp(gdImageSX(image), gdImageSY(image), WBMP_WHITE)) == NULL) {
 		gd_error("Could not create WBMP\n");
-		return;
+		return 1;
 	}
 
 	/* fill up the WBMP structure */
@@ -123,11 +131,15 @@ BGD_DECLARE(void) gdImageWBMPCtx(gdImagePtr image, int fg, gdIOCtx *out)
 
 	/* write the WBMP to a gd file descriptor */
 	if(writewbmp(wbmp, &gd_putout, out)) {
+		freewbmp(wbmp);
 		gd_error("Could not save WBMP\n");
+		return 1;
 	}
 
 	/* des submitted this bugfix: gdFree the memory. */
 	freewbmp(wbmp);
+
+	return 0;
 }
 
 /*
@@ -271,8 +283,11 @@ BGD_DECLARE(void *) gdImageWBMPPtr(gdImagePtr im, int *size, int fg)
 	void *rv;
 	gdIOCtx *out = gdNewDynamicCtx(2048, NULL);
 	if (out == NULL) return NULL;
-	gdImageWBMPCtx(im, fg, out);
-	rv = gdDPExtractData(out, size);
+	if (!_gdImageWBMPCtx(im, fg, out)) {
+		rv = gdDPExtractData(out, size);
+	} else {
+		rv = NULL;
+	}
 	out->gd_free(out);
 	return rv;
 }

programming_language: C++
filename: .gitignore
change_type: ModificationType.MODIFY
diff: @@ -3,5 +3,6 @@
 /jpeg_empty_file
 /jpeg_im2im
 /jpeg_null
+/jpeg_ptr_double_free
 /jpeg_read
 /jpeg_resolution

programming_language: Shell
filename: CMakeLists.txt
change_type: ModificationType.MODIFY
diff: @@ -2,6 +2,7 @@ IF(JPEG_FOUND)
 LIST(APPEND TESTS_FILES
 	jpeg_empty_file
 	jpeg_im2im
+	jpeg_ptr_double_free
 	jpeg_null
 )
 

programming_language: Ruby
filename: Makemodule.am
change_type: ModificationType.MODIFY
diff: @@ -2,7 +2,8 @@ if HAVE_LIBJPEG
 libgd_test_programs += \
 	jpeg/jpeg_empty_file \
 	jpeg/jpeg_im2im \
-	jpeg/jpeg_null
+	jpeg/jpeg_null \
+	jpeg/jpeg_ptr_double_free
 
 if HAVE_LIBPNG
 libgd_test_programs += \

programming_language: Batchfile
filename: jpeg_ptr_double_free.c
change_type: ModificationType.ADD
diff: @@ -0,0 +1,31 @@
+/**
+ * Test that failure to convert to JPEG returns NULL
+ *
+ * We are creating an image, set its width to zero, and pass this image to
+ * `gdImageJpegPtr()` which is supposed to fail, and as such should return NULL.
+ *
+ * See also <https://github.com/libgd/libgd/issues/381>
+ */
+
+
+#include "gd.h"
+#include "gdtest.h"
+
+
+int main()
+{
+    gdImagePtr src, dst;
+    int size;
+
+    src = gdImageCreateTrueColor(1, 10);
+    gdTestAssert(src != NULL);
+
+    src->sx = 0; /* this hack forces gdImageJpegPtr() to fail */
+
+    dst = gdImageJpegPtr(src, &size, 0);
+    gdTestAssert(dst == NULL);
+
+    gdImageDestroy(src);
+
+    return gdNumFailures();
+}

programming_language: C