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
Welcome back! If you're the original bug submitter, here's where you can edit the bug or add additional notes.
If you forgot your password, you can retrieve your password here.
Password:
Status:
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

Pull Requests

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: Tue Oct 08 12:01:26 2024 UTC