Fix GH-19300: Nested array_multisort invocation with error breaks

There are 2 issues:
1. When a MULTISORT_ABORT happens, it frees func, but func may point to
   ARRAYG(multisort_func), which would be a problem with nested
   invocations as it can destroy that of the "parent" invocation.
   To solve this, delay assigning to the globals.
2. The old globals were not restored which means that nested invocations
   with different flags will cause a wrong sorting function to be used.

Closes GH-19319.
This commit is contained in:
Niels Dossche 2025-07-30 19:04:15 +02:00
parent 6fa8a25a40
commit a96b05e63f
No known key found for this signature in database
GPG key ID: B8A8AD166DF0E2E5
4 changed files with 90 additions and 4 deletions

2
NEWS
View file

@ -68,6 +68,8 @@ PHP NEWS
. Fixed OSS Fuzz #433303828 (Leak in failed unserialize() with opcache).
(ilutov)
. Fix theoretical issues with hrtime() not being available. (nielsdos)
. Fixed bug GH-19300 (Nested array_multisort invocation with error breaks).
(nielsdos)
- Windows:
. Free opened_path when opened_path_len >= MAXPATHLEN. (dixyes)

View file

@ -5911,7 +5911,7 @@ PHP_FUNCTION(array_multisort)
for (i = 0; i < MULTISORT_LAST; i++) {
parse_state[i] = 0;
}
func = ARRAYG(multisort_func) = ecalloc(argc, sizeof(bucket_compare_func_t));
func = ecalloc(argc, sizeof(bucket_compare_func_t));
/* Here we go through the input arguments and parse them. Each one can
* be either an array or a sort flag which follows an array. If not
@ -5927,7 +5927,7 @@ PHP_FUNCTION(array_multisort)
/* We see the next array, so we update the sort flags of
* the previous array and reset the sort flags. */
if (i > 0) {
ARRAYG(multisort_func)[num_arrays - 1] = php_get_data_compare_func_unstable(sort_type, sort_order != PHP_SORT_ASC);
func[num_arrays - 1] = php_get_data_compare_func_unstable(sort_type, sort_order != PHP_SORT_ASC);
sort_order = PHP_SORT_ASC;
sort_type = PHP_SORT_REGULAR;
}
@ -5979,8 +5979,6 @@ PHP_FUNCTION(array_multisort)
MULTISORT_ABORT;
}
}
/* Take care of the last array sort flags. */
ARRAYG(multisort_func)[num_arrays - 1] = php_get_data_compare_func_unstable(sort_type, sort_order != PHP_SORT_ASC);
/* Make sure the arrays are of the same size. */
array_size = zend_hash_num_elements(Z_ARRVAL_P(arrays[0]));
@ -5998,6 +5996,11 @@ PHP_FUNCTION(array_multisort)
RETURN_TRUE;
}
/* Take care of the last array sort flags. */
func[num_arrays - 1] = php_get_data_compare_func_unstable(sort_type, sort_order != PHP_SORT_ASC);
bucket_compare_func_t *old_multisort_func = ARRAYG(multisort_func);
ARRAYG(multisort_func) = func;
/* Create the indirection array. This array is of size MxN, where
* M is the number of entries in each input array and N is the number
* of the input arrays + 1. The last column is UNDEF to indicate the end
@ -6074,6 +6077,7 @@ clean_up:
efree(indirect);
efree(func);
efree(arrays);
ARRAYG(multisort_func) = old_multisort_func;
}
/* }}} */

View file

@ -0,0 +1,40 @@
--TEST--
GH-19300 (Nested array_multisort invocation with error breaks) - correct invocation variation
--FILE--
<?php
class MyStringable {
public function __construct(private string $data) {}
public function __tostring() {
array_multisort([]); // Trigger update of array sort globals in happy path
return $this->data;
}
}
$inputs = [
new MyStringable('3'),
new MyStringable('1'),
new MyStringable('2'),
];
var_dump(array_multisort($inputs, SORT_STRING));
var_dump($inputs);
?>
--EXPECT--
bool(true)
array(3) {
[0]=>
object(MyStringable)#2 (1) {
["data":"MyStringable":private]=>
string(1) "1"
}
[1]=>
object(MyStringable)#3 (1) {
["data":"MyStringable":private]=>
string(1) "2"
}
[2]=>
object(MyStringable)#1 (1) {
["data":"MyStringable":private]=>
string(1) "3"
}
}

View file

@ -0,0 +1,40 @@
--TEST--
GH-19300 (Nested array_multisort invocation with error breaks) - error variation
--FILE--
<?php
function error_handle($level, $message, $file = '', $line = 0){
try {
array_multisort($a, SORT_ASC); // Trigger multisort abort
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
}
set_error_handler('error_handle');
$inputs = [
new stdClass,
new stdClass,
new stdClass,
];
var_dump(array_multisort($inputs, SORT_NUMERIC));
var_dump($inputs);
?>
--EXPECT--
array_multisort(): Argument #1 ($array) must be an array or a sort flag
array_multisort(): Argument #1 ($array) must be an array or a sort flag
array_multisort(): Argument #1 ($array) must be an array or a sort flag
array_multisort(): Argument #1 ($array) must be an array or a sort flag
bool(true)
array(3) {
[0]=>
object(stdClass)#1 (0) {
}
[1]=>
object(stdClass)#2 (0) {
}
[2]=>
object(stdClass)#3 (0) {
}
}