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

================= Bug Report (1/2) ==================
## Source: PHP Bugs
## URL: https://bugs.php.net/bug.php?id=72696
## Description:
Description:
------------
Description:
------------
Invalid color causes stack exhaustion by recursive call to function gdImageFillToBorder when the image used is truecolor. This was tested on a 64 bits platform.

This is not the same as bug #72350 but they are related.


GDB output
----------

gdb -q --args /home/operac/php-70-sinasan/sapi/cli/php -n poc.php
Reading symbols from /home/operac/php-70-sinasan/sapi/cli/php...done.
(gdb) b gd.c:1851
Breakpoint 1 at 0x54a354: gd.c:1851. (2 locations)
(gdb) b gd.c:1834
Breakpoint 2 at 0x54a287: gd.c:1834. (2 locations)
(gdb) r
Starting program: /home/operac/php-70-sinasan/sapi/cli/php -n poc.php


Breakpoint 1, php_gd_gdImageFillToBorder (im=0x7ffff2c77000, x=0, y=0, border=1, color=-2) at /home/operac/php-70-sinasan/ext/gd/libgd/gd.c:1851
1851                                            gdImageFillToBorder(im, i, y + 1, border, color);
(gdb) c
Continuing.

Breakpoint 2, php_gd_gdImageFillToBorder (im=0x7ffff2c77000, x=0, y=1, border=1, color=-2) at /home/operac/php-70-sinasan/ext/gd/libgd/gd.c:1834
1834                                            gdImageFillToBorder(im, i, y - 1, border, color);
(gdb) c
Continuing.

Breakpoint 1, php_gd_gdImageFillToBorder (im=0x7ffff2c77000, x=0, y=0, border=1, color=-2) at /home/operac/php-70-sinasan/ext/gd/libgd/gd.c:1851
1851                                            gdImageFillToBorder(im, i, y + 1, border, color);
(gdb) c
Continuing.

Breakpoint 2, php_gd_gdImageFillToBorder (im=0x7ffff2c77000, x=0, y=1, border=1, color=-2) at /home/operac/php-70-sinasan/ext/gd/libgd/gd.c:1834
1834                                            gdImageFillToBorder(im, i, y - 1, border, color);
(gdb) p/x color
$1 = 0xfffffffe

Test script:
---------------
<?php

$imgs = imagecreatetruecolor(10, 10);
imagefilltoborder($imgs, 0, 0, 1, 9223372036854775806);

Expected result:
----------------
No crash

Actual result:
--------------
operac@hp2:~/crashes/067b65f5-filltoborder$ sh run.sh 

ASAN:SIGSEGV
=================================================================
==5658==ERROR: AddressSanitizer: stack-overflow on address 0x7ffd7b0bdf98 (pc 0x000000991208 bp 0x7ffd7b0be0e0 sp 0x7ffd7b0bdf98 T0)
    #0 0x991207 in php_gd_gdImageSetPixel /home/operac/php-70/ext/gd/libgd/gd.c:728
    #1 0x99cb00 in php_gd_gdImageFillToBorder /home/operac/php-70/ext/gd/libgd/gd.c:1810
    #2 0x99d752 in php_gd_gdImageFillToBorder /home/operac/php-70/ext/gd/libgd/gd.c:1834
    #3 0x99dbfa in php_gd_gdImageFillToBorder /home/operac/php-70/ext/gd/libgd/gd.c:1851
    #4 0x99d752 in php_gd_gdImageFillToBorder /home/operac/php-70/ext/gd/libgd/gd.c:1834
    #5 0x99dbfa in php_gd_gdImageFillToBorder /home/operac/php-70/ext/gd/libgd/gd.c:1851
    #6 0x99d752 in php_gd_gdImageFillToBorder /home/operac/php-70/ext/gd/libgd/gd.c:1834
    #7 0x99dbfa in php_gd_gdImageFillToBorder /home/operac/php-70/ext/gd/libgd/gd.c:1851
    #8 0x99d752 in php_gd_gdImageFillToBorder /home/operac/php-70/ext/gd/libgd/gd.c:1834

================= Bug Report (2/2) ==================
## Source: GitHub Issue
## URL: https://github.com/libgd/libgd/issues/215
## Description:
Issue: libgd/libgd#215
Title: gdImageFillToBorder stack-overflow when invalid color is used
State: closed
Created by: fmunozs
Created at: 2016-05-25 11:11:05+00:00
Labels: bug

Issue Body:
Invalid color causes stack exhaustion by recursive call to function gdImageFillToBorder when the image used is not truecolor.

Source code:
https://github.com/php/php-src/blob/master/ext/gd/libgd/gd.c#L1811

```
    if (y > 0) {
        lastBorder = 1;
        for (i = leftLimit; i <= rightLimit; i++) {
            int c = gdImageGetPixel(im, i, y - 1);
            if (lastBorder) {
                if ((c != border) && (c != color)) {
                    gdImageFillToBorder(im, i, y - 1, border, color);
                    lastBorder = 0;
                }
            } else if ((c == border) || (c == color)) {
                lastBorder = 1;
            }
        }
    }

    /* Below */
    if (y < ((im->sy) - 1)) {
        lastBorder = 1;
        for (i = leftLimit; i <= rightLimit; i++) {
            int c = gdImageGetPixel(im, i, y + 1);

            if (lastBorder) {
                if ((c != border) && (c != color)) {
                    gdImageFillToBorder(im, i, y + 1, border, color);
                    lastBorder = 0;
                }
            } else if ((c == border) || (c == color)) {
                lastBorder = 1;
            }
        }
    }
    im->alphaBlendingFlag = restoreAlphaBlending;
```

---

```
gdb -q --args /home/user/php-7.0/sapi/cli/php -n poc.php
Reading symbols from /home/user/php-7.0/sapi/cli/php...done.
(gdb) b gd.c:1811
Breakpoint 1 at 0x8176146: file /home/user/php-7.0/ext/gd/libgd/gd.c, line 1811.
(gdb) b gd.c:1829
Breakpoint 2 at 0x81761f2: file /home/user/php-7.0/ext/gd/libgd/gd.c, line 1829.
(gdb) r
Starting program: /home/user/php-7.0/sapi/cli/php -n poc.php

Breakpoint 2, php_gd_gdImageFillToBorder (im=0xf5a6c000, x=0, y=0, border=1, color=313) at /home/user/php-7.0/ext/gd/libgd/gd.c:1829
1829                                            gdImageFillToBorder(im, i, y + 1, border, color);
(gdb) p c
$1 = 0
(gdb) c
Continuing.

Breakpoint 1, php_gd_gdImageFillToBorder (im=0xf5a6c000, x=0, y=1, border=1, color=313) at /home/user/php-7.0/ext/gd/libgd/gd.c:1811
1811                                    if ((c != border) && (c != color)) {
(gdb) p c
$2 = 57         (different of color 313)
(gdb) c
Continuing.

Breakpoint 2, php_gd_gdImageFillToBorder (im=0xf5a6c000, x=0, y=0, border=1, color=313) at /home/user/php-7.0/ext/gd/libgd/gd.c:1829
1829                                            gdImageFillToBorder(im, i, y + 1, border, color);
(gdb) p c
$3 = 57        (different of color 313)
(gdb) c
Continuing.

Breakpoint 1, php_gd_gdImageFillToBorder (im=0xf5a6c000, x=0, y=1, border=1, color=313) at /home/user/php-7.0/ext/gd/libgd/gd.c:1811
1811                                    if ((c != border) && (c != color)) {
(gdb) p c
$4 = 57        (different of color 313)
```
## Test script:

```
<?php
$img = imagecreate(10, 10);
imagefilltoborder($img, 0, 0, 1, 0x139);
```
## Expected result:

No crash
## Actual result:

```
user@Xenial32-2:~/crashes/fillborder$ /ramdisk/php-71/sapi/cli/php -n poc.php
ASAN:SIGSEGV
=================================================================
==5027==ERROR: AddressSanitizer: stack-overflow on address 0xff276ff0 (pc 0x085ee3f8 bp 0x1e4cdb90 sp 0xff276ff0 T0)
    #0 0x85ee3f7 in php_gd_gdImageSetPixel /home/user/php-7.1asan/ext/gd/libgd/gd.c:755
    #1 0xf266bfff  (<unknown module>)

SUMMARY: AddressSanitizer: stack-overflow /home/user/php-7.1asan/ext/gd/libgd/gd.c:755 php_gd_gdImageSetPixel
==5027==ABORTING
user@Xenial32-2:~/crashes/fillborder$ /ramdisk/php-71/sapi/cli/php -n poc.php
ASAN:SIGSEGV
=================================================================
==5771==ERROR: AddressSanitizer: stack-overflow on address 0xff758ff4 (pc 0x085ebfcf bp 0xfffffffc sp 0xff758ff0 T0)
    #0 0x85ebfce in gdImageTileApply /home/user/php-7.1asan/ext/gd/libgd/gd.c:869
    #1 0x85ebfce in php_gd_gdImageSetPixel /home/user/php-7.1asan/ext/gd/libgd/gd.c:749
    #2 0x85f7eae in php_gd_gdImageFillToBorder /home/user/php-7.1asan/ext/gd/libgd/gd.c:1788
    #3 0x85f8f32 in php_gd_gdImageFillToBorder /home/user/php-7.1asan/ext/gd/libgd/gd.c:1829
    #4 0x85f8e04 in php_gd_gdImageFillToBorder /home/user/php-7.1asan/ext/gd/libgd/gd.c:1812
    #5 0x85f8f32 in php_gd_gdImageFillToBorder /home/user/php-7.1asan/ext/gd/libgd/gd.c:1829
    #6 0x85f8e04 in php_gd_gdImageFillToBorder /home/user/php-7.1asan/ext/gd/libgd/gd.c:1812
    #7 0x85f8f32 in php_gd_gdImageFillToBorder /home/user/php-7.1asan/ext/gd/libgd/gd.c:1829
    #8 0x85f8e04 in php_gd_gdImageFillToBorder /home/user/php-7.1asan/ext/gd/libgd/gd.c:1812
    #9 0x85f8f32 in php_gd_gdImageFillToBorder /home/user/php-7.1asan/ext/gd/libgd/gd.c:1829
    #10 0x85f8e04 in php_gd_gdImageFillToBorder /home/user/php-7.1asan/ext/gd/libgd/gd.c:1812
    #11 0x85f8f32 in php_gd_gdImageFillToBorder /home/user/php-7.1asan/ext/gd/libgd/gd.c:1829
```



Comments:
Comment by vapier on 2016-05-25 14:08:32+00:00:
seems related to the fix for issue #213.  we silently clip the coordinates to the limits of the image now.


---

Comment by fmunozs on 2016-05-25 14:27:03+00:00:
I'm not sure If its the same issue, since I identified it on the libgd version included with php, that fixed #213 first. Also note that I'm no using big numbers on parameters. According to my testing there is no stackoverflow If I use imagecreatetruecolor. 


---

Comment by vapier on 2016-05-25 15:29:56+00:00:
i'm not saying it's the same issue, just somewhat related


---

Comment by fmunozs on 2016-05-25 23:10:01+00:00:
Ok, sorry for the noise then :) language barrier. How does this patch look to you?

```
--- a/ext/gd/libgd/gd.c
+++ b/ext/gd/libgd/gd.c
@@ -1770,6 +1770,8 @@ void gdImageFillToBorder (gdImagePtr im
        restoreAlphaBlending = im->alphaBlendingFlag;
        im->alphaBlendingFlag = 0;

+       if (!im->trueColor) color &= 0xff;
+
        if (x >= im->sx) {
                x = im->sx - 1;
        } else if (x < 0) {
```

It's for php's libgd though.


---

Comment by vapier on 2016-05-26 04:38:29+00:00:
that will make it work in the non-truecolor mode, but i think truecolor still has issues.  when you factor in the different effect modes (alpha-blending, overlays, multiply), doing a set+get on a pixel might not return the exact same value you put in (`color` in this case).

one thing i really hate about gd's API in general is its inconsistent (and uncommon) ability to signal errors.  ideally, `gdImageFillToBorder` would return an error whenever sanity was violated:
- `x` or `y` coordinates out of bounds
- `border` is invalid
- `color` is invalid for image


---

Comment by pierrejoye on 2016-06-02 11:51:16+00:00:
@vapier totally agree, sadly remaining of the old APIs where we try to keep BC.

also correct fix could be:

```
if (!im->trueColor) {
    if ((color >= gdMaxColors) && (color > im->colorsTotal)) {
        return;
    }
}
```


---

Comment by pierrejoye on 2016-06-02 12:13:24+00:00:
@vapier about tc colors, this function overrides the modes, it only works in full replace mode, no blending is ever done. 

See https://github.com/libgd/libgd/blob/502e4cd873c3b37b307b9f450ef827d40916c3d6/src/gd.c#L1939


---

Comment by vapier on 2016-06-03 02:49:07+00:00:
ah yeah, i missed `gdImageFillToBorder` clobbering `alphaBlendingFlag`.  that addresses my concern.

i'm not sure about that fix -- if the user requested a color that is outside of `colorsTotal`, shouldn't we reject that too ?  the `gdImageFill` func enforces things this way too:

``` c
    if (!im->trueColor && nc > (im->colorsTotal - 1)) {
        return;
    }
```

i think we want to use that in this func.


---

Comment by pierrejoye on 2016-06-03 04:49:26+00:00:
Yes that works too. Is color unsigned or signed? If the latter we must check for >0 too.


---

Comment by vapier on 2016-06-03 15:41:39+00:00:
`colorsTotal` is an `int` as is `nc` and `color`, so `gdImageFill` will be comparing `nc` against `-1` in the initial (common?) case.

it's not clear to me how a value of `0` should be handled with `colorsTotal`.  should we be initializing that to `256` instead in `gdImageCreate` ?


---

Comment by pierrejoye on 2016-06-04 03:29:37+00:00:
colorsTotal is the currently amount of colors allocated (see the color
allocate function). 256 is the maximum amount of colors. Both being used
for palette images.
On Jun 3, 2016 10:41 PM, "Mike Frysinger" notifications@github.com wrote:

> colorsTotal is an int as is nc and color, so gdImageFill will be
> comparing nc against -1 in the initial (common?) case.
> 
> it's not clear to me how a value of 0 should be handled with colorsTotal.
> should we be initializing that to 256 instead in gdImageCreate ?
> 
> —
> You are receiving this because you commented.
> Reply to this email directly, view it on GitHub
> https://github.com/libgd/libgd/issues/215#issuecomment-223614372, or mute
> the thread
> https://github.com/notifications/unsubscribe/AARPKHngKjw7haLn10v4se99bO8UANUEks5qIEs1gaJpZM4ImbEz
> .


---

Comment by vapier on 2016-06-04 16:41:49+00:00:
i think you forgot to `git add` the test so the build now fails ...

also, i think the question is still open: should `gdImageCreate` be defaulting `colorsTotal` to `256` instead of `0` ?  if we do that, we can make more assumptions elsewhere and not treat `0` specially.


---

Comment by pierrejoye on 2016-06-05 04:17:33+00:00:
We need the amount of colors allocated when it comes to save the image. We can indeed scan the image to see if a color index is used or not but it can be very slow.

Also 0 is a special case and have to remain one as it is used at the background color (the 1st allocate will be the default bgd color for palette image, why gdCalloc is used to allocate the rows).


Commit References:
502e4cd873c3b37b307b9f450ef827d40916c3d6

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 73f4e0339abda6514600c0c6a633ad8c49005437
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;
}