Skip to content

Commit a96b05e

Browse files
committed
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.
1 parent 6fa8a25 commit a96b05e

File tree

4 files changed

+90
-4
lines changed

4 files changed

+90
-4
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ PHP NEWS
6868
. Fixed OSS Fuzz #433303828 (Leak in failed unserialize() with opcache).
6969
(ilutov)
7070
. Fix theoretical issues with hrtime() not being available. (nielsdos)
71+
. Fixed bug GH-19300 (Nested array_multisort invocation with error breaks).
72+
(nielsdos)
7173

7274
- Windows:
7375
. Free opened_path when opened_path_len >= MAXPATHLEN. (dixyes)

ext/standard/array.c

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5911,7 +5911,7 @@ PHP_FUNCTION(array_multisort)
59115911
for (i = 0; i < MULTISORT_LAST; i++) {
59125912
parse_state[i] = 0;
59135913
}
5914-
func = ARRAYG(multisort_func) = ecalloc(argc, sizeof(bucket_compare_func_t));
5914+
func = ecalloc(argc, sizeof(bucket_compare_func_t));
59155915

59165916
/* Here we go through the input arguments and parse them. Each one can
59175917
* be either an array or a sort flag which follows an array. If not
@@ -5927,7 +5927,7 @@ PHP_FUNCTION(array_multisort)
59275927
/* We see the next array, so we update the sort flags of
59285928
* the previous array and reset the sort flags. */
59295929
if (i > 0) {
5930-
ARRAYG(multisort_func)[num_arrays - 1] = php_get_data_compare_func_unstable(sort_type, sort_order != PHP_SORT_ASC);
5930+
func[num_arrays - 1] = php_get_data_compare_func_unstable(sort_type, sort_order != PHP_SORT_ASC);
59315931
sort_order = PHP_SORT_ASC;
59325932
sort_type = PHP_SORT_REGULAR;
59335933
}
@@ -5979,8 +5979,6 @@ PHP_FUNCTION(array_multisort)
59795979
MULTISORT_ABORT;
59805980
}
59815981
}
5982-
/* Take care of the last array sort flags. */
5983-
ARRAYG(multisort_func)[num_arrays - 1] = php_get_data_compare_func_unstable(sort_type, sort_order != PHP_SORT_ASC);
59845982

59855983
/* Make sure the arrays are of the same size. */
59865984
array_size = zend_hash_num_elements(Z_ARRVAL_P(arrays[0]));
@@ -5998,6 +5996,11 @@ PHP_FUNCTION(array_multisort)
59985996
RETURN_TRUE;
59995997
}
60005998

5999+
/* Take care of the last array sort flags. */
6000+
func[num_arrays - 1] = php_get_data_compare_func_unstable(sort_type, sort_order != PHP_SORT_ASC);
6001+
bucket_compare_func_t *old_multisort_func = ARRAYG(multisort_func);
6002+
ARRAYG(multisort_func) = func;
6003+
60016004
/* Create the indirection array. This array is of size MxN, where
60026005
* M is the number of entries in each input array and N is the number
60036006
* of the input arrays + 1. The last column is UNDEF to indicate the end
@@ -6074,6 +6077,7 @@ PHP_FUNCTION(array_multisort)
60746077
efree(indirect);
60756078
efree(func);
60766079
efree(arrays);
6080+
ARRAYG(multisort_func) = old_multisort_func;
60776081
}
60786082
/* }}} */
60796083

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
--TEST--
2+
GH-19300 (Nested array_multisort invocation with error breaks) - correct invocation variation
3+
--FILE--
4+
<?php
5+
class MyStringable {
6+
public function __construct(private string $data) {}
7+
public function __tostring() {
8+
array_multisort([]); // Trigger update of array sort globals in happy path
9+
return $this->data;
10+
}
11+
}
12+
13+
$inputs = [
14+
new MyStringable('3'),
15+
new MyStringable('1'),
16+
new MyStringable('2'),
17+
];
18+
19+
var_dump(array_multisort($inputs, SORT_STRING));
20+
var_dump($inputs);
21+
?>
22+
--EXPECT--
23+
bool(true)
24+
array(3) {
25+
[0]=>
26+
object(MyStringable)#2 (1) {
27+
["data":"MyStringable":private]=>
28+
string(1) "1"
29+
}
30+
[1]=>
31+
object(MyStringable)#3 (1) {
32+
["data":"MyStringable":private]=>
33+
string(1) "2"
34+
}
35+
[2]=>
36+
object(MyStringable)#1 (1) {
37+
["data":"MyStringable":private]=>
38+
string(1) "3"
39+
}
40+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
--TEST--
2+
GH-19300 (Nested array_multisort invocation with error breaks) - error variation
3+
--FILE--
4+
<?php
5+
6+
function error_handle($level, $message, $file = '', $line = 0){
7+
try {
8+
array_multisort($a, SORT_ASC); // Trigger multisort abort
9+
} catch (TypeError $e) {
10+
echo $e->getMessage(), "\n";
11+
}
12+
}
13+
set_error_handler('error_handle');
14+
15+
$inputs = [
16+
new stdClass,
17+
new stdClass,
18+
new stdClass,
19+
];
20+
21+
var_dump(array_multisort($inputs, SORT_NUMERIC));
22+
var_dump($inputs);
23+
?>
24+
--EXPECT--
25+
array_multisort(): Argument #1 ($array) must be an array or a sort flag
26+
array_multisort(): Argument #1 ($array) must be an array or a sort flag
27+
array_multisort(): Argument #1 ($array) must be an array or a sort flag
28+
array_multisort(): Argument #1 ($array) must be an array or a sort flag
29+
bool(true)
30+
array(3) {
31+
[0]=>
32+
object(stdClass)#1 (0) {
33+
}
34+
[1]=>
35+
object(stdClass)#2 (0) {
36+
}
37+
[2]=>
38+
object(stdClass)#3 (0) {
39+
}
40+
}

0 commit comments

Comments
 (0)