Skip to content

Commit 38e60e4

Browse files
Fix GH-20836: Stack overflow in mb_convert_variables with recursive array references
1 parent 1c9f117 commit 38e60e4

File tree

3 files changed

+85
-11
lines changed

3 files changed

+85
-11
lines changed

NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ PHP NEWS
22
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
33
?? ??? ????, PHP 8.4.18
44

5+
- Mbstring:
6+
. Fixed bug GH-20836 (Stack overflow in mb_convert_variables with
7+
recursive array references). (alexandre-daubois)
8+
59
- Readline:
610
. Fixed bug GH-18139 (Memory leak when overriding some settings
711
via readline_info()). (ndossche)

ext/mbstring/mbstring.c

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3691,11 +3691,26 @@ PHP_FUNCTION(mb_convert_kana)
36913691
RETVAL_STR(jp_kana_convert(str, enc, opt));
36923692
}
36933693

3694+
static zend_always_inline bool mb_check_stack_limit(void)
3695+
{
3696+
#ifdef ZEND_CHECK_STACK_LIMIT
3697+
if (UNEXPECTED(zend_call_stack_overflowed(EG(stack_limit)))) {
3698+
zend_call_stack_size_error();
3699+
return true;
3700+
}
3701+
#endif
3702+
return false;
3703+
}
3704+
36943705
static unsigned int mb_recursive_count_strings(zval *var)
36953706
{
36963707
unsigned int count = 0;
36973708
ZVAL_DEREF(var);
36983709

3710+
if (mb_check_stack_limit()) {
3711+
return 0;
3712+
}
3713+
36993714
if (Z_TYPE_P(var) == IS_STRING) {
37003715
count++;
37013716
} else if (Z_TYPE_P(var) == IS_ARRAY || Z_TYPE_P(var) == IS_OBJECT) {
@@ -3726,6 +3741,10 @@ static bool mb_recursive_find_strings(zval *var, const unsigned char **val_list,
37263741
{
37273742
ZVAL_DEREF(var);
37283743

3744+
if (mb_check_stack_limit()) {
3745+
return true;
3746+
}
3747+
37293748
if (Z_TYPE_P(var) == IS_STRING) {
37303749
val_list[*count] = (const unsigned char*)Z_STRVAL_P(var);
37313750
len_list[*count] = Z_STRLEN_P(var);
@@ -3763,6 +3782,10 @@ static bool mb_recursive_convert_variable(zval *var, const mbfl_encoding* from_e
37633782
{
37643783
zval *entry, *orig_var;
37653784

3785+
if (mb_check_stack_limit()) {
3786+
return true;
3787+
}
3788+
37663789
orig_var = var;
37673790
ZVAL_DEREF(var);
37683791

@@ -3771,17 +3794,25 @@ static bool mb_recursive_convert_variable(zval *var, const mbfl_encoding* from_e
37713794
zval_ptr_dtor(orig_var);
37723795
ZVAL_STR(orig_var, ret);
37733796
} else if (Z_TYPE_P(var) == IS_ARRAY || Z_TYPE_P(var) == IS_OBJECT) {
3774-
if (Z_TYPE_P(var) == IS_ARRAY) {
3775-
SEPARATE_ARRAY(var);
3776-
}
3777-
if (Z_REFCOUNTED_P(var)) {
3778-
if (Z_IS_RECURSIVE_P(var)) {
3797+
HashTable *ht = HASH_OF(var);
3798+
HashTable *orig_ht = ht;
3799+
3800+
if (ht) {
3801+
if (GC_IS_RECURSIVE(ht)) {
37793802
return true;
37803803
}
3781-
Z_PROTECT_RECURSION_P(var);
3804+
3805+
GC_TRY_PROTECT_RECURSION(ht);
37823806
}
37833807

3784-
HashTable *ht = HASH_OF(var);
3808+
if (Z_TYPE_P(var) == IS_ARRAY) {
3809+
SEPARATE_ARRAY(var);
3810+
ht = Z_ARRVAL_P(var);
3811+
3812+
if (ht && ht != orig_ht && !GC_IS_RECURSIVE(ht)) {
3813+
GC_TRY_PROTECT_RECURSION(ht);
3814+
}
3815+
}
37853816
if (ht != NULL) {
37863817
ZEND_HASH_FOREACH_VAL(ht, entry) {
37873818
/* Can be a typed property declaration, in which case we need to remove the reference from the source list.
@@ -3800,16 +3831,22 @@ static bool mb_recursive_convert_variable(zval *var, const mbfl_encoding* from_e
38003831
}
38013832

38023833
if (mb_recursive_convert_variable(entry, from_encoding, to_encoding)) {
3803-
if (Z_REFCOUNTED_P(var)) {
3804-
Z_UNPROTECT_RECURSION_P(var);
3834+
if (ht && ht != orig_ht) {
3835+
GC_TRY_UNPROTECT_RECURSION(ht);
3836+
}
3837+
if (orig_ht) {
3838+
GC_TRY_UNPROTECT_RECURSION(orig_ht);
38053839
}
38063840
return true;
38073841
}
38083842
} ZEND_HASH_FOREACH_END();
38093843
}
38103844

3811-
if (Z_REFCOUNTED_P(var)) {
3812-
Z_UNPROTECT_RECURSION_P(var);
3845+
if (ht && ht != orig_ht) {
3846+
GC_TRY_UNPROTECT_RECURSION(ht);
3847+
}
3848+
if (orig_ht) {
3849+
GC_TRY_UNPROTECT_RECURSION(orig_ht);
38133850
}
38143851
}
38153852

ext/mbstring/tests/gh20836.phpt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
--TEST--
2+
GH-20836 (Stack overflow in mb_convert_variables with recursive array references)
3+
--EXTENSIONS--
4+
mbstring
5+
--FILE--
6+
<?php
7+
8+
$a = [];
9+
$b = [];
10+
$a[] = $b[] = &$a;
11+
var_dump(mb_convert_variables('utf-8', 'utf-8', $a));
12+
13+
$c = [];
14+
$c[] = &$c;
15+
var_dump(mb_convert_variables('utf-8', 'utf-8', $c));
16+
17+
$normal = ['test', 'array'];
18+
var_dump(mb_convert_variables('utf-8', 'utf-8', $normal));
19+
20+
$d = ['level1' => ['level2' => ['level3' => 'data']]];
21+
var_dump(mb_convert_variables('utf-8', 'utf-8', $d));
22+
23+
echo "Done\n";
24+
?>
25+
--EXPECTF--
26+
Warning: mb_convert_variables(): Cannot handle recursive references in %s on line %d
27+
bool(false)
28+
29+
Warning: mb_convert_variables(): Cannot handle recursive references in %s on line %d
30+
bool(false)
31+
string(5) "UTF-8"
32+
string(5) "UTF-8"
33+
Done

0 commit comments

Comments
 (0)