Project: libgd (libgd/libgd), language: c++
Vulnerability instance: libgd.cve-2017-6362
Sanitizer: address

================= Bug Report (1/1) ==================
## Source: GitHub Issue
## URL: https://github.com/libgd/libgd/issues/381
## Description:
Issue: libgd/libgd#381
Title: libgd double-free vulnerability
State: closed
Created by: varsleak
Created at: 2017-02-14 09:42:07+00:00
Labels: bug

Issue Body:
# Description
I find the libgd double-free vulnerability when call gdImagePngPtr function.

# Environment
Ubuntu x64 16.04
libgd 2.2.4 (Latest commit e65415d91c57ba8d5d90ec9e22a95ae2416c62f9)

# Detail
```
➜  gcc_asan_build git:(master) ✗ Bin/test_webp_bug_double_free
GD Warning: gd-png error: no colors in palette
=================================================================
==9574==ERROR: AddressSanitizer: attempting **double-free** on 0x61d00001ea80 in thread T0:
    #0 0x7f39d2d5e2ca in __interceptor_free (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x982ca)
    #1 0x7f39d2a63d19 in gdFree /home/varsleak/github/libgd/src/gdhelpers.c:115
    #2 0x7f39d2a37551 in gdReallocDynamic /home/varsleak/github/libgd/src/gd_io_dp.c:412
    #3 0x7f39d2a37662 in trimDynamic /home/varsleak/github/libgd/src/gd_io_dp.c:427
    #4 0x7f39d2a35fca in gdDPExtractData /home/varsleak/github/libgd/src/gd_io_dp.c:127
    #5 0x7f39d2a440e1 in gdImagePngPtr /home/varsleak/github/libgd/src/gd_png.c:661
    #6 0x400b06 in main /home/varsleak/github/libgd/tests/webp/bug_double_free.c:19
    #7 0x7f39d262582f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #8 0x400968 in _start (/home/varsleak/github/libgd/gcc_asan_build/Bin/test_webp_bug_double_free+0x400968)

0x61d00001ea80 is located 0 bytes inside of 2048-byte region [0x61d00001ea80,0x61d00001f280)
freed by thread T0 here:
    #0 0x7f39d2d5e961 in realloc (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x98961)
    #1 0x7f39d2a63cb8 in gdRealloc /home/varsleak/github/libgd/src/gdhelpers.c:81
    #2 0x7f39d2a373e8 in gdReallocDynamic /home/varsleak/github/libgd/src/gd_io_dp.c:397
    #3 0x7f39d2a37662 in trimDynamic /home/varsleak/github/libgd/src/gd_io_dp.c:427
    #4 0x7f39d2a35fca in gdDPExtractData /home/varsleak/github/libgd/src/gd_io_dp.c:127
    #5 0x7f39d2a440e1 in gdImagePngPtr /home/varsleak/github/libgd/src/gd_png.c:661
    #6 0x400b06 in main /home/varsleak/github/libgd/tests/webp/bug_double_free.c:19
    #7 0x7f39d262582f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)

previously allocated by thread T0 here:
    #0 0x7f39d2d5e602 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x98602)
    #1 0x7f39d2a63c93 in gdMalloc /home/varsleak/github/libgd/src/gdhelpers.c:75
    #2 0x7f39d2a36e8a in allocDynamic /home/varsleak/github/libgd/src/gd_io_dp.c:327
    #3 0x7f39d2a366f5 in newDynamic /home/varsleak/github/libgd/src/gd_io_dp.c:230
    #4 0x7f39d2a35d74 in gdNewDynamicCtxEx /home/varsleak/github/libgd/src/gd_io_dp.c:91
    #5 0x7f39d2a35d2d in gdNewDynamicCtx /home/varsleak/github/libgd/src/gd_io_dp.c:75
    #6 0x7f39d2a440a4 in gdImagePngPtr /home/varsleak/github/libgd/src/gd_png.c:658
    #7 0x400b06 in main /home/varsleak/github/libgd/tests/webp/bug_double_free.c:19
    #8 0x7f39d262582f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)

SUMMARY: AddressSanitizer: double-free ??:0 __interceptor_free
==9574==ABORTING
```

the c code
```
#include "gd.h"
#include "gdtest.h"


int main() {
    gdImagePtr im1 = 0;
    void * im2 = 0;
    int size = 0;

    im1 = gdImageCreate(100, 100);

    im2 = gdImagePngPtr(im1, &size);

    gdFree(im2);

    gdImageDestroy(im1);

    return 0;
}
```



Comments:
Comment by cmb69 on 2017-02-14 11:41:26+00:00:
The problem is that gdImagePngPtr() calls [gdImangePngCtxEx()](https://github.com/libgd/libgd/blob/gd-2.2.4/src/gd_png.c#L660), but the latter [bails out](https://github.com/libgd/libgd/blob/gd-2.2.4/src/gd_png.c#L848) because there are no colors in the palette. However, gdImagePngCtxEx() doesn't provide a meaningful return value, so gdImagePngPtr() can't check whether the operation succeeded.

It seems that we need a fix analogous to commt a49feeae. Other image output functions might be affected as well.

---

Comment by cmb69 on 2017-02-14 11:42:21+00:00:
To clarify: this is not a security issue, but rather a programmer error, but still it should be fixed.

---

Comment by varsleak on 2017-02-15 02:14:23+00:00:
OK. thank you very much.

---

Comment by varsleak on 2017-02-17 17:08:39+00:00:
I find the double-free vulnerability again, the file leading to the crash is [double-free.txt](https://github.com/libgd/libgd/files/783833/double-free.txt).

```
int main(int argc, char * argv[])
{
    gdImagePtr im = 0;
    FILE *fp = 0;
    void * im2 = 0;
    int size = 0;

    if (argc != 2) return -1;

    fp = fopen(argv[1], "rb");
    if (fp){
	    im = gdImageCreateFromGd(fp);

	    if (im){
	    	im2 = gdImagePngPtr(im, &size);

	    	if (im2){
	    		gdFree(im2);
	    	}
	    	gdImageDestroy(im);
		}
	    
	}

    fclose(fp);

    return gdNumFailures();
}
```

---

Comment by cmb69 on 2017-02-17 17:17:17+00:00:
Why did you close this ticket? I assume that was by accident, so i'm re-opening.

---

Comment by varsleak on 2017-02-18 01:22:06+00:00:
Yes, that's an accident.
I'm so sorry.

---

Comment by varsleak on 2017-02-24 10:33:19+00:00:
Analysis
======
The second argument(int required)  of the gdReallocDynamic function is 0.

Step 1: at if((newPtr = gdRealloc(dp->data, required))) { , dp->data will be free and the newPtr will be set NULL due to required is 0.

Step 2: newPtr = gdMalloc(required); newPtr be set  new memory space. funny is the newPtr and dp->data are the same address.

Step 3:  memcpy is work.

Step 4:  gdFree(dp->data) ,**notice** newPtr and dp->data is same. The first free occured,

Step 5:  return dp->data to previous function.

The end: 
Under normal circumstances we should free this pointer. The second free. occured.
```
static int gdReallocDynamic(dynamicPtr *dp, int required)
{
	void *newPtr;

	/* First try gdRealloc().  If that doesn't work, make a new
	 * memory block and copy. */
	if((newPtr = gdRealloc(dp->data, required))) {
		dp->realSize = required;
		dp->data = newPtr;
		return TRUE;
	}

	/* create a new pointer */
	newPtr = gdMalloc(required);
	if(!newPtr) {
		dp->dataGood = FALSE;
		return FALSE;
	}

	/* copy the old data into it */
	memcpy(newPtr, dp->data, dp->logicalSize);
	gdFree(dp->data);                           **<- the first free.**
	dp->data = newPtr;

	dp->realSize = required;
	return TRUE;
}

.....


int main(int argc, char * argv[])
{
    gdImagePtr im = 0;
    FILE *fp = 0;
    void * im2 = 0;
    int size = 0;

    if (argc != 2) return -1;

    fp = fopen(argv[1], "rb");
    if (fp){
	    im = gdImageCreateFromGd(fp);

	    if (im){
	    	im2 = gdImagePngPtr(im, &size);

	    	if (im2){
	    		gdFree(im2);               **<- the second free.**
	    	}
	    	gdImageDestroy(im);
		}
	    
	}

    fclose(fp);

    return gdNumFailures();
}

```


---

Comment by varsleak on 2017-02-28 06:45:07+00:00:
CVE-2017-6362 was assigned

---

Comment by pierrejoye on 2017-05-04 07:31:14+00:00:
Is this bug still valid?

---

Comment by cmb69 on 2017-05-04 11:06:33+00:00:
@pierrejoye Yes, still valid (I've send a patch in <41c4ecbc-f5a4-4592-8a74-b4b1155ae2d5@gmx.de>). PHP's bundled libgd is affected as well, so we should coordinate the releases. I'm not sure what to do for PHP 5.6. Is there any schedule for 5.6.31?

---

Comment by cmb69 on 2017-08-27 15:07:39+00:00:
> (I've send a patch in 41c4ecbc-f5a4-4592-8a74-b4b1155ae2d5@gmx.de).

I did, but this was about a completely unrelated issue.

PS:

> this is not a security issue, but rather a programmer error, but still it should be fixed.

As the second example (importing a GD image) shows, this is a security issue.

---

Comment by cmb69 on 2017-08-27 15:34:53+00:00:
> PHP's bundled libgd is affected as well, so we should coordinate the releases.

No. Actually, `gdImagePngPtr(Ex)` is not used by PHP's GD extension at all.

Attachments:
https://github.com/libgd/libgd/files/783833/double-free.txt

Commit References:
e65415d91c57ba8d5d90ec9e22a95ae2416c62f9

Build script:
#!/bin/bash -eu
# Minimized build script with only core build commands
set -eu
./bootstrap.sh
./configure --prefix="$WORK" --disable-shared
make -j$(nproc) install

Dockerfile:
FROM hwiwonlee/secb.base:latest
RUN apt-get update && apt-get install -y make autoconf automake libtool pkg-config libz-dev
RUN apt-get install -y make autoconf automake libtool pkg-config libz-dev
RUN git clone https://github.com/libgd/libgd libgd
RUN git -C libgd checkout de09874b15a4c88772db35ced842330f5de23e76
WORKDIR $SRC/libgd
COPY build.sh $SRC/

Additional files:
filename: parser_target.cc
content: // Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
/////////////////////////////////////////////////////////////////////////////

#include <stdint.h>
#include "gd.h"

#define PASTE(x) gdImageCreateFrom ## x ## Ptr
#define CREATE_IMAGE(FORMAT) PASTE(FORMAT)

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
    gdImagePtr im = CREATE_IMAGE(FUZZ_GD_FORMAT)(Size, (void*) Data);
    if (im) gdImageDestroy(im);
    return 0;
}