Bug #79930 [Com]: array_merge_recursive() crashes when called with array with single reference
| From: | imbolk at gmail dot com | Date: | Wed, 05 Aug 2020 12:04:11 +0000 |
| Subject: | Bug #79930 [Com]: array_merge_recursive() crashes when called with array with single reference | ||
| References: | 1 | Groups: | php.bugs |
| Request: | Send a blank email to php-bugs+get-228413@lists.php.net to get a copy of this message | ||
Edit report at https://bugs.php.net/bug.php?id=79930&edit=1
ID: 79930
Comment by: imbolk at gmail dot com
Reported by: tandre@php.net
Summary: array_merge_recursive() crashes when called with
array with single reference
Status: Open
Type: Bug
Package: Reproducible crash
Operating System: Linux(any?)
PHP Version: 7.4.9RC1
Block user comment: N
Private report: N
New Comment:
PHP 7.2 also affected but 7.1
Previous Comments:
------------------------------------------------------------------------
[2020-08-05 00:22:29] 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)
------------------------------------------------------------------------
--
Edit this bug report at https://bugs.php.net/bug.php?id=79930&edit=1