The following PHP code triggers a reliable heap buffer overflow:
$arr = range(0, 2**29);
array_merge($arr, $arr, $arr, $arr, $arr, $arr, $arr, $arr);
Or the general case:
$power = 24; // You can choose this number 1 <= $power < 32
$arr = range(0, 2**$power);
array_merge(...array_fill(0, 2**(32-$power), $arr));
This is because packed arrays in array_merge() have an optimization that requires precomputing the count of the elements, done using count += zend_hash_num_elements(Z_ARRVAL_P(arg));. However, this may overflow. Note that even if it didn't, it would cause a DoS because array_init() will trigger a bailout with too large counts.
The code provided is artificial, but I believe this can happen in the real world with for example large sets of JSON data that are then passed to array_merge(). The total count just has to exceed HT_MAX_SIZE to trigger the DoS or the 32-bit limit to trigger the buffer overflow.
Impact
If we were using a memory safe language we would "just" be limited to the DoS, but now we have heap corruption and all the joys of it.
This bug has been in PHP since PHP 7.1.
Workarounds
Prevent a large number of inputs of long arrays to array_merge().
Security impact
As noted the example code is just a PoC exploit trigger.
The JSON file could be crafted and loaded via json_decode that results in a structure like: [array1, array2, array3, ...]. Then the code could use array_merge(array1, array2, array3, ...) from the JSON data. The arrays itself may be relatively small but if the sum of elements exceed the HT_MAX_SIZE bound, the buffer overflow is triggered.
Something like this:
<?php
$array = json_decode(file_get_contents('input.json')); // array of arrays
array_merge(...$array);
The following PHP code triggers a reliable heap buffer overflow:
Or the general case:
This is because packed arrays in array_merge() have an optimization that requires precomputing the count of the elements, done using
count += zend_hash_num_elements(Z_ARRVAL_P(arg));. However, this may overflow. Note that even if it didn't, it would cause a DoS because array_init() will trigger a bailout with too large counts.The code provided is artificial, but I believe this can happen in the real world with for example large sets of JSON data that are then passed to array_merge(). The total count just has to exceed HT_MAX_SIZE to trigger the DoS or the 32-bit limit to trigger the buffer overflow.
Impact
If we were using a memory safe language we would "just" be limited to the DoS, but now we have heap corruption and all the joys of it.
This bug has been in PHP since PHP 7.1.
Workarounds
Prevent a large number of inputs of long arrays to array_merge().
Security impact
As noted the example code is just a PoC exploit trigger.
The JSON file could be crafted and loaded via json_decode that results in a structure like: [array1, array2, array3, ...]. Then the code could use
array_merge(array1, array2, array3, ...)from the JSON data. The arrays itself may be relatively small but if the sum of elements exceed the HT_MAX_SIZE bound, the buffer overflow is triggered.Something like this: