For the second time in a row, I find a bug in PHP.

$ USE_ZEND_ALLOC=0 $(phpenv which php) vendor/bin/phpunit tests/ContainerTest.php --debug
PHPUnit 4.8.36 by Sebastian Bergmann and contributors.


Starting test 'Slim\Tests\ContainerTest::testGet'.
.
Starting test 'Slim\Tests\ContainerTest::testGetWithValueNotFoundError'.
.
Starting test 'Slim\Tests\ContainerTest::testGetWithDiConfigErrorThrownAsContainerValueNotFoundException'.
.
Starting test 'Slim\Tests\ContainerTest::testGetWithDiConfigErrorThrownAsInvalidArgumentException'.
.
Starting test 'Slim\Tests\ContainerTest::testGetWithErrorThrownByFactoryClosure'.
.
Starting test 'Slim\Tests\ContainerTest::testGetRequest'.
.
Starting test 'Slim\Tests\ContainerTest::testGetResponse'.
.
Starting test 'Slim\Tests\ContainerTest::testGetRouter'.
.
Starting test 'Slim\Tests\ContainerTest::testGetErrorHandler'.
.
Starting test 'Slim\Tests\ContainerTest::testGetNotAllowedHandler'.
.
Starting test 'Slim\Tests\ContainerTest::testSettingsCanBeEdited'.
.
Starting test 'Slim\Tests\ContainerTest::testMagicIssetMethod'.
.
Starting test 'Slim\Tests\ContainerTest::testMagicGetMethod'.
.
Starting test 'Slim\Tests\ContainerTest::testRouteCacheDisabledByDefault'.
.

Time: 56 ms, Memory: 0.00MB

OK (14 tests, 15 assertions)
*** Error in /home/volodymyr/.phpenv/versions/7.1.17-nts-debug/bin/php': corrupted size vs. prev_size: 0x000055732d1ed91f ***
Aborted (core dumped)
</pre>

Without USE_ZEND_ALLOC=0` everything works.

Sometimes I hate PHP.

Valgrind shows a long error list, which looks like this:
==5164== Invalid read of size 4
==5164==    at 0x97D15B: gc_mark_grey (zend_gc.c:517)
==5164==    by 0x97D125: gc_mark_grey (zend_gc.c:511)
==5164==    by 0x97D452: gc_mark_roots (zend_gc.c:598)
==5164==    by 0x97E605: zend_gc_collect_cycles (zend_gc.c:1072)
==5164==    by 0x92B3B5: shutdown_executor (zend_execute_API.c:358)
==5164==    by 0x945738: zend_deactivate (zend.c:1005)
==5164==    by 0x8AB007: php_request_shutdown (main.c:1902)
==5164==    by 0xA31844: do_cli (php_cli.c:1160)
==5164==    by 0xA320C6: main (php_cli.c:1381)
==5164==  Address 0x10f3e300 is 0 bytes inside a block of size 24 free'd
==5164==    at 0x4C30D3B: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==5164==    by 0x90D976: _efree (zend_alloc.c:2444)
==5164==    by 0x960695: list_entry_destructor (zend_list.c:189)
==5164==    by 0x95C04B: _zend_hash_del_el_ex (zend_hash.c:997)
==5164==    by 0x95C8C7: zend_hash_index_del (zend_hash.c:1210)
==5164==    by 0x960144: zend_list_delete (zend_list.c:50)
==5164==    by 0x8CAA2C: _php_stream_free (streams.c:449)
==5164==    by 0x8CA973: _php_stream_free (streams.c:410)
==5164==    by 0x8CD808: stream_resource_regular_dtor (streams.c:1619)
==5164==    by 0x960213: zend_resource_dtor (zend_list.c:76)
==5164==    by 0x9607DE: zend_close_rsrc (zend_list.c:230)
==5164==    by 0x95D9F3: zend_hash_reverse_apply (zend_hash.c:1598)
==5164==    by 0x960804: zend_close_rsrc_list (zend_list.c:238)
==5164==    by 0x92B374: shutdown_executor (zend_execute_API.c:353)
==5164==    by 0x945738: zend_deactivate (zend.c:1005)
==5164==    by 0x8AB007: php_request_shutdown (main.c:1902)
==5164==    by 0xA31844: do_cli (php_cli.c:1160)
==5164==    by 0xA320C6: main (php_cli.c:1381)
==5164==  Block was alloc'd at
==5164==    at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==5164==    by 0x90E622: __zend_malloc (zend_alloc.c:2838)
==5164==    by 0x90D8CF: _emalloc (zend_alloc.c:2429)
==5164==    by 0x960067: zend_list_insert (zend_list.c:43)
==5164==    by 0x9602A9: zend_register_resource (zend_list.c:98)
==5164==    by 0x8CA71B: _php_stream_alloc (streams.c:310)
==5164==    by 0x8D164F: _php_stream_temp_create_ex (memory.c:579)
==5164==    by 0x8D171B: _php_stream_temp_create (memory.c:591)
==5164==    by 0x7D939A: php_stream_url_wrap_php (php_fopen_wrapper.c:211)
==5164==    by 0x8CEB88: _php_stream_open_wrapper_ex (streams.c:2055)
==5164==    by 0x77A89E: php_if_fopen (file.c:870)
==5164==    by 0x65A8B8: phar_fopen (func_interceptors.c:427)
==5164==    by 0x9AB437: ZEND_DO_FCALL_BY_NAME_SPEC_RETVAL_USED_HANDLER (zend_vm_execute.h:876)
==5164==    by 0x9AA21C: execute_ex (zend_vm_execute.h:429)
==5164==    by 0x92CF09: zend_call_function (zend_execute_API.c:855)
==5164==    by 0x97034E: zend_call_method (zend_interfaces.c:99)
==5164==    by 0x996108: zend_std_read_dimension (zend_object_handlers.c:819)
==5164==    by 0x9A76BC: zend_fetch_dimension_address_read (zend_execute.c:1874)
==5164==    by 0x9A7861: zend_fetch_dimension_address_read_R (zend_execute.c:1898)
==5164==    by 0x9D373D: ZEND_FETCH_DIM_FUNC_ARG_SPEC_VAR_CONST_HANDLER (zend_vm_execute.h:18552)
==5164==    by 0x9AA21C: execute_ex (zend_vm_execute.h:429)
==5164==    by 0x92CF09: zend_call_function (zend_execute_API.c:855)
==5164==    by 0x694EEA: reflection_method_invoke (php_reflection.c:3342)
==5164==    by 0x6950BA: zim_reflection_method_invokeArgs (php_reflection.c:3378)
==5164==    by 0x9ABE80: ZEND_DO_FCALL_SPEC_RETVAL_USED_HANDLER (zend_vm_execute.h:1097)
==5164==    by 0x9AA21C: execute_ex (zend_vm_execute.h:429)
==5164==    by 0x9AA32D: zend_execute (zend_vm_execute.h:474)
==5164==    by 0x946D7B: zend_execute_scripts (zend.c:1482)
==5164==    by 0x8AC4EB: php_execute_script (main.c:2577)
==5164==    by 0xA30EF2: do_cli (php_cli.c:993)
==5164==
==5164== Invalid write of size 4
==5164==    at 0x97D164: gc_mark_grey (zend_gc.c:517)
==5164==    by 0x97D125: gc_mark_grey (zend_gc.c:511)
==5164==    by 0x97D452: gc_mark_roots (zend_gc.c:598)
==5164==    by 0x97E605: zend_gc_collect_cycles (zend_gc.c:1072)
==5164==    by 0x92B3B5: shutdown_executor (zend_execute_API.c:358)
==5164==    by 0x945738: zend_deactivate (zend.c:1005)
==5164==    by 0x8AB007: php_request_shutdown (main.c:1902)
==5164==    by 0xA31844: do_cli (php_cli.c:1160)
==5164==    by 0xA320C6: main (php_cli.c:1381)
==5164==  Address 0x10f3e300 is 0 bytes inside a block of size 24 free'd
==5164==    at 0x4C30D3B: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==5164==    by 0x90D976: _efree (zend_alloc.c:2444)
==5164==    by 0x960695: list_entry_destructor (zend_list.c:189)
==5164==    by 0x95C04B: _zend_hash_del_el_ex (zend_hash.c:997)
==5164==    by 0x95C8C7: zend_hash_index_del (zend_hash.c:1210)
==5164==    by 0x960144: zend_list_delete (zend_list.c:50)
==5164==    by 0x8CAA2C: _php_stream_free (streams.c:449)
==5164==    by 0x8CA973: _php_stream_free (streams.c:410)
==5164==    by 0x8CD808: stream_resource_regular_dtor (streams.c:1619)
==5164==    by 0x960213: zend_resource_dtor (zend_list.c:76)
==5164==    by 0x9607DE: zend_close_rsrc (zend_list.c:230)
==5164==    by 0x95D9F3: zend_hash_reverse_apply (zend_hash.c:1598)
==5164==    by 0x960804: zend_close_rsrc_list (zend_list.c:238)
==5164==    by 0x92B374: shutdown_executor (zend_execute_API.c:353)
==5164==    by 0x945738: zend_deactivate (zend.c:1005)
==5164==    by 0x8AB007: php_request_shutdown (main.c:1902)
==5164==    by 0xA31844: do_cli (php_cli.c:1160)
==5164==    by 0xA320C6: main (php_cli.c:1381)
==5164==  Block was alloc'd at
==5164==    at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==5164==    by 0x90E622: __zend_malloc (zend_alloc.c:2838)
==5164==    by 0x90D8CF: _emalloc (zend_alloc.c:2429)
==5164==    by 0x960067: zend_list_insert (zend_list.c:43)
==5164==    by 0x9602A9: zend_register_resource (zend_list.c:98)
==5164==    by 0x8CA71B: _php_stream_alloc (streams.c:310)
==5164==    by 0x8D164F: _php_stream_temp_create_ex (memory.c:579)
==5164==    by 0x8D171B: _php_stream_temp_create (memory.c:591)
==5164==    by 0x7D939A: php_stream_url_wrap_php (php_fopen_wrapper.c:211)
==5164==    by 0x8CEB88: _php_stream_open_wrapper_ex (streams.c:2055)
==5164==    by 0x77A89E: php_if_fopen (file.c:870)
==5164==    by 0x65A8B8: phar_fopen (func_interceptors.c:427)
==5164==    by 0x9AB437: ZEND_DO_FCALL_BY_NAME_SPEC_RETVAL_USED_HANDLER (zend_vm_execute.h:876)
==5164==    by 0x9AA21C: execute_ex (zend_vm_execute.h:429)
==5164==    by 0x92CF09: zend_call_function (zend_execute_API.c:855)
==5164==    by 0x97034E: zend_call_method (zend_interfaces.c:99)
==5164==    by 0x996108: zend_std_read_dimension (zend_object_handlers.c:819)
==5164==    by 0x9A76BC: zend_fetch_dimension_address_read (zend_execute.c:1874)
==5164==    by 0x9A7861: zend_fetch_dimension_address_read_R (zend_execute.c:1898)
==5164==    by 0x9D373D: ZEND_FETCH_DIM_FUNC_ARG_SPEC_VAR_CONST_HANDLER (zend_vm_execute.h:18552)
==5164==    by 0x9AA21C: execute_ex (zend_vm_execute.h:429)
==5164==    by 0x92CF09: zend_call_function (zend_execute_API.c:855)
==5164==    by 0x694EEA: reflection_method_invoke (php_reflection.c:3342)
==5164==    by 0x6950BA: zim_reflection_method_invokeArgs (php_reflection.c:3378)
==5164==    by 0x9ABE80: ZEND_DO_FCALL_SPEC_RETVAL_USED_HANDLER (zend_vm_execute.h:1097)
==5164==    by 0x9AA21C: execute_ex (zend_vm_execute.h:429)
==5164==    by 0x9AA32D: zend_execute (zend_vm_execute.h:474)
==5164==    by 0x946D7B: zend_execute_scripts (zend.c:1482)
==5164==    by 0x8AC4EB: php_execute_script (main.c:2577)
==5164==    by 0xA30EF2: do_cli (php_cli.c:993)

It looks like the garbage collector is fed with already destroyed data.

Affected PHP versions: 7.0.30 (earlier versions are probably affected as well), 7.1.11 — 7.1.17 (earlier versions are probably affected as well), 7.2.x branch is not affected.

I guess I need a minimal test case to report the bug.

Here is the minimal test case:

<?php
class Body
{
	private $r;

	public function __construct($x)
	{
		$this->r = $x;
	}
}

class Pimple_Container implements ArrayAccess
{
	private $values = [];
	private $raw    = [];

	public function offsetGet($id)
	{
		$raw = $this->values[$id];
		$this->values[$id] = $raw($this);
		$this->raw[$id] = $raw;
		return $this->values[$id];
	}

	public function offsetSet($id, $val)
	{
		$this->values[$id] = $val;
	}

	public function offsetUnset($id) {}
	public function offsetExists($id) {}
}

class Container extends Pimple_Container
{
	public function __construct()
	{
		$this['body'] = function($c) {
			return new Body(fopen('php://temp', 'r+'));
		};
	}

	public function get($id)
	{
		return $this[$id];
	}
}

$c = new Container();
$response = $c['body'];

Bug report: https://bugs.php.net/bug.php?id=76302

Update: this fixes the issue for me:

--- 7.1.17/main/streams/streams.c.orig  2018-05-05 21:13:42.470900381 +0300
+++ 7.1.17/main/streams/streams.c       2018-05-05 21:10:26.585014131 +0300
@@ -408,7 +408,7 @@
                 * enclosing stream can free this stream. We remove rsrc_dtor because
                 * we want the enclosing stream to be deleted from the resource list */
                return php_stream_free(enclosing_stream,
-                       (close_options | PHP_STREAM_FREE_CALL_DTOR) & ~PHP_STREAM_FREE_RSRC_DTOR);
+                       (close_options | PHP_STREAM_FREE_CALL_DTOR | PHP_STREAM_FREE_KEEP_RSRC) & ~PHP_STREAM_FREE_RSRC_DTOR);
        }

        /* if we are releasing the stream only (and preserving the underlying handle),
One More Bug in PHP
Tagged on:         

Leave a Reply

Your email address will not be published. Required fields are marked *