3 https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-6978
5 Patch copied from upstream source repository:
7 https://github.com/libgd/libgd/commit/553702980ae89c83f2d6e254d62cf82e204956d0
9 From 553702980ae89c83f2d6e254d62cf82e204956d0 Mon Sep 17 00:00:00 2001
10 From: "Christoph M. Becker" <cmbecker69@gmx.de>
11 Date: Thu, 17 Jan 2019 11:54:55 +0100
12 Subject: [PATCH] Fix #492: Potential double-free in gdImage*Ptr()
14 Whenever `gdImage*Ptr()` calls `gdImage*Ctx()` and the latter fails, we
15 must not call `gdDPExtractData()`; otherwise a double-free would
16 happen. Since `gdImage*Ctx()` are void functions, and we can't change
17 that for BC reasons, we're introducing static helpers which are used
20 We're adding a regression test for `gdImageJpegPtr()`, but not for
21 `gdImageGifPtr()` and `gdImageWbmpPtr()` since we don't know how to
22 trigger failure of the respective `gdImage*Ctx()` calls.
24 This potential security issue has been reported by Solmaz Salimi (aka.
27 src/gd_gif_out.c | 18 +++++++++++++++---
28 src/gd_jpeg.c | 20 ++++++++++++++++----
29 src/gd_wbmp.c | 21 ++++++++++++++++++---
30 tests/jpeg/.gitignore | 1 +
31 tests/jpeg/CMakeLists.txt | 1 +
32 tests/jpeg/Makemodule.am | 3 ++-
33 tests/jpeg/jpeg_ptr_double_free.c | 31 +++++++++++++++++++++++++++++++
34 7 files changed, 84 insertions(+), 11 deletions(-)
35 create mode 100644 tests/jpeg/jpeg_ptr_double_free.c
37 diff --git a/src/gd_gif_out.c b/src/gd_gif_out.c
38 index 298a581..d5a9534 100644
39 --- a/src/gd_gif_out.c
40 +++ b/src/gd_gif_out.c
41 @@ -99,6 +99,7 @@ static void char_init(GifCtx *ctx);
42 static void char_out(int c, GifCtx *ctx);
43 static void flush_char(GifCtx *ctx);
45 +static int _gdImageGifCtx(gdImagePtr im, gdIOCtxPtr out);
49 @@ -131,8 +132,11 @@ BGD_DECLARE(void *) gdImageGifPtr(gdImagePtr im, int *size)
51 gdIOCtx *out = gdNewDynamicCtx(2048, NULL);
52 if (out == NULL) return NULL;
53 - gdImageGifCtx(im, out);
54 - rv = gdDPExtractData(out, size);
55 + if (!_gdImageGifCtx(im, out)) {
56 + rv = gdDPExtractData(out, size);
63 @@ -220,6 +224,12 @@ BGD_DECLARE(void) gdImageGif(gdImagePtr im, FILE *outFile)
66 BGD_DECLARE(void) gdImageGifCtx(gdImagePtr im, gdIOCtxPtr out)
68 + _gdImageGifCtx(im, out);
71 +/* returns 0 on success, 1 on failure */
72 +static int _gdImageGifCtx(gdImagePtr im, gdIOCtxPtr out)
74 gdImagePtr pim = 0, tim = im;
75 int interlace, BitsPerPixel;
76 @@ -231,7 +241,7 @@ BGD_DECLARE(void) gdImageGifCtx(gdImagePtr im, gdIOCtxPtr out)
77 based temporary image. */
78 pim = gdImageCreatePaletteFromTrueColor(im, 1, 256);
85 @@ -247,6 +257,8 @@ BGD_DECLARE(void) gdImageGifCtx(gdImagePtr im, gdIOCtxPtr out)
86 /* Destroy palette based temporary image. */
94 diff --git a/src/gd_jpeg.c b/src/gd_jpeg.c
95 index fc05842..96ef430 100644
98 @@ -117,6 +117,8 @@ static void fatal_jpeg_error(j_common_ptr cinfo)
102 +static int _gdImageJpegCtx(gdImagePtr im, gdIOCtx *outfile, int quality);
105 * Write IM to OUTFILE as a JFIF-formatted JPEG image, using quality
106 * QUALITY. If QUALITY is in the range 0-100, increasing values
107 @@ -231,8 +233,11 @@ BGD_DECLARE(void *) gdImageJpegPtr(gdImagePtr im, int *size, int quality)
109 gdIOCtx *out = gdNewDynamicCtx(2048, NULL);
110 if (out == NULL) return NULL;
111 - gdImageJpegCtx(im, out, quality);
112 - rv = gdDPExtractData(out, size);
113 + if (!_gdImageJpegCtx(im, out, quality)) {
114 + rv = gdDPExtractData(out, size);
121 @@ -253,6 +258,12 @@ void jpeg_gdIOCtx_dest(j_compress_ptr cinfo, gdIOCtx *outfile);
124 BGD_DECLARE(void) gdImageJpegCtx(gdImagePtr im, gdIOCtx *outfile, int quality)
126 + _gdImageJpegCtx(im, outfile, quality);
129 +/* returns 0 on success, 1 on failure */
130 +static int _gdImageJpegCtx(gdImagePtr im, gdIOCtx *outfile, int quality)
132 struct jpeg_compress_struct cinfo;
133 struct jpeg_error_mgr jerr;
134 @@ -287,7 +298,7 @@ BGD_DECLARE(void) gdImageJpegCtx(gdImagePtr im, gdIOCtx *outfile, int quality)
142 cinfo.err->emit_message = jpeg_emit_message;
143 @@ -328,7 +339,7 @@ BGD_DECLARE(void) gdImageJpegCtx(gdImagePtr im, gdIOCtx *outfile, int quality)
145 gd_error("gd-jpeg: error: unable to allocate JPEG row structure: gdCalloc returns NULL\n");
146 jpeg_destroy_compress(&cinfo);
152 @@ -405,6 +416,7 @@ BGD_DECLARE(void) gdImageJpegCtx(gdImagePtr im, gdIOCtx *outfile, int quality)
153 jpeg_finish_compress(&cinfo);
154 jpeg_destroy_compress(&cinfo);
160 diff --git a/src/gd_wbmp.c b/src/gd_wbmp.c
161 index f19a1c9..a49bdbe 100644
164 @@ -88,6 +88,8 @@ int gd_getin(void *in)
165 return (gdGetC((gdIOCtx *)in));
168 +static int _gdImageWBMPCtx(gdImagePtr image, int fg, gdIOCtx *out);
171 Function: gdImageWBMPCtx
173 @@ -100,6 +102,12 @@ int gd_getin(void *in)
174 out - the stream where to write
176 BGD_DECLARE(void) gdImageWBMPCtx(gdImagePtr image, int fg, gdIOCtx *out)
178 + _gdImageWBMPCtx(image, fg, out);
181 +/* returns 0 on success, 1 on failure */
182 +static int _gdImageWBMPCtx(gdImagePtr image, int fg, gdIOCtx *out)
186 @@ -107,7 +115,7 @@ BGD_DECLARE(void) gdImageWBMPCtx(gdImagePtr image, int fg, gdIOCtx *out)
187 /* create the WBMP */
188 if((wbmp = createwbmp(gdImageSX(image), gdImageSY(image), WBMP_WHITE)) == NULL) {
189 gd_error("Could not create WBMP\n");
194 /* fill up the WBMP structure */
195 @@ -123,11 +131,15 @@ BGD_DECLARE(void) gdImageWBMPCtx(gdImagePtr image, int fg, gdIOCtx *out)
197 /* write the WBMP to a gd file descriptor */
198 if(writewbmp(wbmp, &gd_putout, out)) {
200 gd_error("Could not save WBMP\n");
204 /* des submitted this bugfix: gdFree the memory. */
211 @@ -271,8 +283,11 @@ BGD_DECLARE(void *) gdImageWBMPPtr(gdImagePtr im, int *size, int fg)
213 gdIOCtx *out = gdNewDynamicCtx(2048, NULL);
214 if (out == NULL) return NULL;
215 - gdImageWBMPCtx(im, fg, out);
216 - rv = gdDPExtractData(out, size);
217 + if (!_gdImageWBMPCtx(im, fg, out)) {
218 + rv = gdDPExtractData(out, size);
225 #diff --git a/tests/jpeg/.gitignore b/tests/jpeg/.gitignore
226 #index c28aa87..13bcf04 100644
227 #--- a/tests/jpeg/.gitignore
228 #+++ b/tests/jpeg/.gitignore
233 #+/jpeg_ptr_double_free
236 diff --git a/tests/jpeg/CMakeLists.txt b/tests/jpeg/CMakeLists.txt
237 index 19964b0..a8d8162 100644
238 --- a/tests/jpeg/CMakeLists.txt
239 +++ b/tests/jpeg/CMakeLists.txt
240 @@ -2,6 +2,7 @@ IF(JPEG_FOUND)
241 LIST(APPEND TESTS_FILES
244 + jpeg_ptr_double_free
248 diff --git a/tests/jpeg/Makemodule.am b/tests/jpeg/Makemodule.am
249 index 7e5d317..b89e169 100644
250 --- a/tests/jpeg/Makemodule.am
251 +++ b/tests/jpeg/Makemodule.am
252 @@ -2,7 +2,8 @@ if HAVE_LIBJPEG
253 libgd_test_programs += \
254 jpeg/jpeg_empty_file \
258 + jpeg/jpeg_ptr_double_free
261 libgd_test_programs += \
262 diff --git a/tests/jpeg/jpeg_ptr_double_free.c b/tests/jpeg/jpeg_ptr_double_free.c
264 index 0000000..df5a510
266 +++ b/tests/jpeg/jpeg_ptr_double_free.c
269 + * Test that failure to convert to JPEG returns NULL
271 + * We are creating an image, set its width to zero, and pass this image to
272 + * `gdImageJpegPtr()` which is supposed to fail, and as such should return NULL.
274 + * See also <https://github.com/libgd/libgd/issues/381>
284 + gdImagePtr src, dst;
287 + src = gdImageCreateTrueColor(1, 10);
288 + gdTestAssert(src != NULL);
290 + src->sx = 0; /* this hack forces gdImageJpegPtr() to fail */
292 + dst = gdImageJpegPtr(src, &size, 0);
293 + gdTestAssert(dst == NULL);
295 + gdImageDestroy(src);
297 + return gdNumFailures();