php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #79930 array_merge_recursive() crashes when called with array with single reference
Submitted: 2020-08-05 00:22 UTC Modified: 2020-08-05 13:23 UTC
Votes:1
Avg. Score:5.0 ± 0.0
Reproduced:1 of 1 (100.0%)
Same Version:1 (100.0%)
Same OS:1 (100.0%)
From: tandre@php.net Assigned: nikic (profile)
Status: Closed Package: Reproducible crash
PHP Version: 7.4.9RC1 OS: Linux(any?)
Private report: No CVE-ID: None
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: tandre@php.net
New email:
PHP Version: OS:

 

 [2020-08-05 00:22 UTC] tandre@php.net
Description:
------------
When array_merge_recursive is called where the second array contains values which are PHP reference groups of size 1 to values that can be freed, it can free values prematurely.
For example, the below snippet creates an array with a reference group of size 1 to a temporary mutable string 'a'.

	$b = ['value' => strtolower('A')];
	array_walk($b, static function (){});

I noticed this when investigating https://github.com/igbinary/igbinary/issues/278

PHP 7.3 is also affected - it can produce invalid output or a segmentation fault

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

call_user_func(function() {
	// The strtolower is one way to create a value that isn't a constant literal generated
	// by the compiler.
	$b = [
		'value' => strtolower('A'),
	];

	array_walk($b, static function (){});

	for ($i = 0; $i < 30; $i++) {
		echo "i=$i\n";
		$m = array_merge_recursive(['value' => 'a'], $b);
	}

	var_dump($a, $b, $m);
});

Expected result:
----------------
This produces valid output and does not crash

Actual result:
--------------
This crashes in NTS non-debug, prints memory leaks in NTS debug for 7.4.10-dev

When running in valgrind with php 7.4.10-dev NTS debug with `USE_ZEND_ALLOC=0 valgrind php  error.php`, I see

ยป USE_ZEND_ALLOC=0 valgrind php error.php 
==4839== Memcheck, a memory error detector
==4839== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==4839== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==4839== Command: php error.php
==4839== 
i=0
i=1
i=2
==4839== Invalid read of size 4
==4839==    at 0xAE68B2: zend_gc_delref (zend_types.h:1039)
==4839==    by 0xAE70A9: i_zval_ptr_dtor (zend_variables.h:43)
==4839==    by 0xAEC3C4: zend_array_destroy (zend_hash.c:1611)
==4839==    by 0xAD00C4: rc_dtor_func (zend_variables.c:57)
==4839==    by 0xAE70B9: i_zval_ptr_dtor (zend_variables.h:44)
==4839==    by 0xAEC3F6: zend_array_destroy (zend_hash.c:1615)
==4839==    by 0xAD00C4: rc_dtor_func (zend_variables.c:57)
==4839==    by 0xB383F6: zend_assign_to_variable (zend_execute.h:131)
==4839==    by 0xBA36D1: ZEND_ASSIGN_SPEC_CV_VAR_RETVAL_UNUSED_HANDLER (zend_vm_execute.h:45260)
==4839==    by 0xBB1684: execute_ex (zend_vm_execute.h:57440)
==4839==    by 0xBB1D4B: zend_execute (zend_vm_execute.h:57856)
==4839==    by 0xAD4770: zend_execute_scripts (zend.c:1677)
==4839==  Address 0x11f85590 is 0 bytes inside a block of size 32 free'd
==4839==    at 0x4C2EDEB: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==4839==    by 0xA99066: _efree_custom (zend_alloc.c:2426)
==4839==    by 0xA991A7: _efree (zend_alloc.c:2546)
==4839==    by 0xAD01C5: zend_string_destroy (zend_variables.c:67)
==4839==    by 0xAD00C4: rc_dtor_func (zend_variables.c:57)
==4839==    by 0xAE70B9: i_zval_ptr_dtor (zend_variables.h:44)
==4839==    by 0xAEC3C4: zend_array_destroy (zend_hash.c:1611)
==4839==    by 0xAD00C4: rc_dtor_func (zend_variables.c:57)
==4839==    by 0xAE70B9: i_zval_ptr_dtor (zend_variables.h:44)
==4839==    by 0xAEC3F6: zend_array_destroy (zend_hash.c:1615)
==4839==    by 0xAD00C4: rc_dtor_func (zend_variables.c:57)
==4839==    by 0xB383F6: zend_assign_to_variable (zend_execute.h:131)
==4839==  Block was alloc'd at
==4839==    at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==4839==    by 0xA9A0A7: __zend_malloc (zend_alloc.c:2976)
==4839==    by 0xA98FFF: _malloc_custom (zend_alloc.c:2417)
==4839==    by 0xA9912D: _emalloc (zend_alloc.c:2536)
==4839==    by 0x922D61: zend_string_alloc (zend_string.h:133)
==4839==    by 0x929DFE: php_string_tolower (string.c:1486)
==4839==    by 0x92A146: zif_strtolower (string.c:1516)
==4839==    by 0xB47BEC: ZEND_DO_ICALL_SPEC_RETVAL_USED_HANDLER (zend_vm_execute.h:1314)
==4839==    by 0xBADC0D: execute_ex (zend_vm_execute.h:53740)
==4839==    by 0xBB1D4B: zend_execute (zend_vm_execute.h:57856)
==4839==    by 0xAD4770: zend_execute_scripts (zend.c:1677)
==4839==    by 0xA355BB: php_execute_script (main.c:2621)
==4839== 
php: /path/to/php-src/Zend/zend_types.h:1039: zend_gc_delref: Assertion `p->refcount > 0' failed.
==4839== 
==4839== Process terminating with default action of signal 6 (SIGABRT)
==4839==    at 0x9D49438: raise (raise.c:54)
==4839==    by 0x9D4B039: abort (abort.c:89)
==4839==    by 0x9D41BE6: __assert_fail_base (assert.c:92)
==4839==    by 0x9D41C91: __assert_fail (assert.c:101)
==4839==    by 0xAE68D6: zend_gc_delref (zend_types.h:1039)
==4839==    by 0xAE70A9: i_zval_ptr_dtor (zend_variables.h:43)
==4839==    by 0xAEC3C4: zend_array_destroy (zend_hash.c:1611)
==4839==    by 0xAD00C4: rc_dtor_func (zend_variables.c:57)
==4839==    by 0xAE70B9: i_zval_ptr_dtor (zend_variables.h:44)
==4839==    by 0xAEC3F6: zend_array_destroy (zend_hash.c:1615)
==4839==    by 0xAD00C4: rc_dtor_func (zend_variables.c:57)
==4839==    by 0xB383F6: zend_assign_to_variable (zend_execute.h:131)
==4839== 
==4839== HEAP SUMMARY:
==4839==     in use at exit: 3,329,941 bytes in 24,367 blocks
==4839==   total heap usage: 28,842 allocs, 4,475 frees, 4,574,768 bytes allocated
==4839== 
==4839== LEAK SUMMARY:
==4839==    definitely lost: 24 bytes in 1 blocks
==4839==    indirectly lost: 0 bytes in 0 blocks
==4839==      possibly lost: 2,389,714 bytes in 18,644 blocks
==4839==    still reachable: 940,203 bytes in 5,722 blocks
==4839==         suppressed: 0 bytes in 0 blocks
==4839== Rerun with --leak-check=full to see details of leaked memory
==4839== 
==4839== For counts of detected and suppressed errors, rerun with: -v
==4839== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)


Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2020-08-05 12:04 UTC] imbolk at gmail dot com
PHP 7.2 also affected but 7.1
 [2020-08-05 13:23 UTC] nikic@php.net
-Assigned To: +Assigned To: nikic
 [2020-08-05 13:44 UTC] nikic@php.net
Automatic comment on behalf of nikita.ppv@gmail.com
Revision: http://git.php.net/?p=php-src.git;a=commit;h=da786a22af1632272d08a98352da9bbaecbe438a
Log: Fixed bug #79930
 [2020-08-05 13:44 UTC] nikic@php.net
-Status: Assigned +Status: Closed
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Thu Apr 25 08:01:28 2024 UTC