Skip to content

Commit

Permalink
Fix array going away during sorting
Browse files Browse the repository at this point in the history
  • Loading branch information
iluuu1994 committed Nov 4, 2024
1 parent 2985de7 commit 2bdce61
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 10 deletions.
1 change: 1 addition & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ PHP NEWS
(ilutov)
. Fixed bug GH-16508 (Incorrect line number in inheritance errors of delayed
early bound classes). (ilutov)
. Fixed bug GH-16648 (Use-after-free during array sorting). (ilutov)

- Curl:
. Fixed bug GH-16302 (CurlMultiHandle holds a reference to CurlHandle if
Expand Down
49 changes: 49 additions & 0 deletions Zend/tests/gh16648.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
--TEST--
GH-16648: Use-after-free during array sorting
--FILE--
<?php

function resize_arr() {
global $arr;
for ($i = 0; $i < 10; $i++) {
$arr[$i] = $i;
}
}

class C {
function __toString() {
resize_arr();
return '3';
}
}

$arr = ['a' => '1', '3' => new C, '2' => '2'];
asort($arr);
var_dump($arr);

?>
--EXPECT--
array(11) {
["a"]=>
string(1) "1"
[3]=>
int(3)
[2]=>
int(2)
[0]=>
int(0)
[1]=>
int(1)
[4]=>
int(4)
[5]=>
int(5)
[6]=>
int(6)
[7]=>
int(7)
[8]=>
int(8)
[9]=>
int(9)
}
30 changes: 28 additions & 2 deletions Zend/zend_hash.c
Original file line number Diff line number Diff line change
Expand Up @@ -2863,13 +2863,12 @@ ZEND_API void zend_hash_bucket_packed_swap(Bucket *p, Bucket *q)
q->h = h;
}

ZEND_API void ZEND_FASTCALL zend_hash_sort_ex(HashTable *ht, sort_func_t sort, bucket_compare_func_t compar, bool renumber)
static void zend_hash_sort_internal(HashTable *ht, sort_func_t sort, bucket_compare_func_t compar, bool renumber)
{
Bucket *p;
uint32_t i, j;

IS_CONSISTENT(ht);
HT_ASSERT_RC1(ht);

if (!(ht->nNumOfElements>1) && !(renumber && ht->nNumOfElements>0)) {
/* Doesn't require sorting */
Expand Down Expand Up @@ -2956,6 +2955,33 @@ ZEND_API void ZEND_FASTCALL zend_hash_sort_ex(HashTable *ht, sort_func_t sort, b
}
}

ZEND_API void ZEND_FASTCALL zend_hash_sort_ex(HashTable *ht, sort_func_t sort, bucket_compare_func_t compar, bool renumber)
{
HT_ASSERT_RC1(ht);
zend_hash_sort_internal(ht, sort, compar, renumber);
}

void ZEND_FASTCALL zend_array_sort_ex(HashTable *ht, sort_func_t sort, bucket_compare_func_t compar, bool renumber)
{
HT_ASSERT_RC1(ht);

/* Unpack the array early to avoid RCn assertion failures. */
if (HT_IS_PACKED(ht)) {
zend_hash_packed_to_hash(ht);
}

/* Adding a refcount prevents the array from going away. */
GC_ADDREF(ht);

zend_hash_sort_internal(ht, sort, compar, renumber);

if (UNEXPECTED(GC_DELREF(ht) == 0)) {
zend_array_destroy(ht);
} else {
gc_check_possible_root((zend_refcounted *)ht);
}
}

static zend_always_inline int zend_hash_compare_impl(HashTable *ht1, HashTable *ht2, compare_func_t compar, bool ordered) {
uint32_t idx1, idx2;
zend_string *key1, *key2;
Expand Down
8 changes: 8 additions & 0 deletions Zend/zend_hash.h
Original file line number Diff line number Diff line change
Expand Up @@ -296,12 +296,20 @@ ZEND_API void zend_hash_bucket_packed_swap(Bucket *p, Bucket *q);
typedef int (*bucket_compare_func_t)(Bucket *a, Bucket *b);
ZEND_API int zend_hash_compare(HashTable *ht1, HashTable *ht2, compare_func_t compar, bool ordered);
ZEND_API void ZEND_FASTCALL zend_hash_sort_ex(HashTable *ht, sort_func_t sort_func, bucket_compare_func_t compare_func, bool renumber);
void ZEND_FASTCALL zend_array_sort_ex(HashTable *ht, sort_func_t sort_func, bucket_compare_func_t compare_func, bool renumber);
ZEND_API zval* ZEND_FASTCALL zend_hash_minmax(const HashTable *ht, compare_func_t compar, uint32_t flag);

static zend_always_inline void ZEND_FASTCALL zend_hash_sort(HashTable *ht, bucket_compare_func_t compare_func, zend_bool renumber) {
zend_hash_sort_ex(ht, zend_sort, compare_func, renumber);
}

/* Use this variant over zend_hash_sort() when sorting user arrays that may
* trigger user code. It will ensure the user code cannot free the array during
* sorting. */
static zend_always_inline void zend_array_sort(HashTable *ht, bucket_compare_func_t compare_func, zend_bool renumber) {
zend_array_sort_ex(ht, zend_sort, compare_func, renumber);
}

static zend_always_inline uint32_t zend_hash_num_elements(const HashTable *ht) {
return ht->nNumOfElements;
}
Expand Down
2 changes: 1 addition & 1 deletion ext/intl/collator/collator_sort.c
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ static void collator_sort_internal( int renumber, INTERNAL_FUNCTION_PARAMETERS )
INTL_G( current_collator ) = co->ucoll;

/* Sort specified array. */
zend_hash_sort(hash, collator_compare_func, renumber);
zend_array_sort(hash, collator_compare_func, renumber);

/* Restore saved collator. */
INTL_G( current_collator ) = saved_collator;
Expand Down
14 changes: 7 additions & 7 deletions ext/standard/array.c
Original file line number Diff line number Diff line change
Expand Up @@ -705,9 +705,9 @@ static void php_natsort(INTERNAL_FUNCTION_PARAMETERS, int fold_case) /* {{{ */
ZEND_PARSE_PARAMETERS_END();

if (fold_case) {
zend_hash_sort(Z_ARRVAL_P(array), php_array_natural_case_compare, 0);
zend_array_sort(Z_ARRVAL_P(array), php_array_natural_case_compare, 0);
} else {
zend_hash_sort(Z_ARRVAL_P(array), php_array_natural_compare, 0);
zend_array_sort(Z_ARRVAL_P(array), php_array_natural_compare, 0);
}

RETURN_TRUE;
Expand Down Expand Up @@ -743,7 +743,7 @@ PHP_FUNCTION(asort)

cmp = php_get_data_compare_func(sort_type, 0);

zend_hash_sort(Z_ARRVAL_P(array), cmp, 0);
zend_array_sort(Z_ARRVAL_P(array), cmp, 0);

RETURN_TRUE;
}
Expand All @@ -764,7 +764,7 @@ PHP_FUNCTION(arsort)

cmp = php_get_data_compare_func(sort_type, 1);

zend_hash_sort(Z_ARRVAL_P(array), cmp, 0);
zend_array_sort(Z_ARRVAL_P(array), cmp, 0);

RETURN_TRUE;
}
Expand All @@ -785,7 +785,7 @@ PHP_FUNCTION(sort)

cmp = php_get_data_compare_func(sort_type, 0);

zend_hash_sort(Z_ARRVAL_P(array), cmp, 1);
zend_array_sort(Z_ARRVAL_P(array), cmp, 1);

RETURN_TRUE;
}
Expand All @@ -806,7 +806,7 @@ PHP_FUNCTION(rsort)

cmp = php_get_data_compare_func(sort_type, 1);

zend_hash_sort(Z_ARRVAL_P(array), cmp, 1);
zend_array_sort(Z_ARRVAL_P(array), cmp, 1);

RETURN_TRUE;
}
Expand Down Expand Up @@ -904,7 +904,7 @@ static void php_usort(INTERNAL_FUNCTION_PARAMETERS, bucket_compare_func_t compar
/* Copy array, so the in-place modifications will not be visible to the callback function */
arr = zend_array_dup(arr);

zend_hash_sort(arr, compare_func, renumber);
zend_array_sort(arr, compare_func, renumber);

zval garbage;
ZVAL_COPY_VALUE(&garbage, array);
Expand Down

0 comments on commit 2bdce61

Please sign in to comment.