Skip to content

Commit a970eef

Browse files
committed
Fix GH-11874: intl causing segfault in docker images
The segfault happens because zoi->wrapping_obj points to an object that has been freed. This wrapping_obj is set in IntlIterator_from_StringEnumeration(). Notice how the refcount is not increased in this function. By switching to ZVAL_OBJ_COPY, the segfault disappears. We also need to move the responsibility of destroying the iterator to the iterator itself and keep the object data destruction in the object destruction. The existing code used a weird recursive destruction between the iterator and object that was too hard to understand to be honest. This patch simplifies everything and in the process gets rid of the leak. Iterators that are embedded are now responsible for their own memory cleanup. Closes GH-17343.
1 parent 412a6b2 commit a970eef

File tree

4 files changed

+49
-25
lines changed

4 files changed

+49
-25
lines changed

NEWS

+3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ PHP NEWS
55
- FTP:
66
. Fixed bug GH-16800 (ftp functions can abort with EINTR). (nielsdos)
77

8+
- Intl:
9+
. Fixed bug GH-11874 (intl causing segfault in docker images). (nielsdos)
10+
811
02 Jan 2025, PHP 8.3.16RC1
912

1013
- Core:

ext/intl/breakiterator/breakiterator_iterators.cpp

+14-2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ inline BreakIterator *_breakiter_prolog(zend_object_iterator *iter)
5151
static void _breakiterator_destroy_it(zend_object_iterator *iter)
5252
{
5353
zval_ptr_dtor(&iter->data);
54+
/* Don't free iter here because it is allocated as an object on its own, not embedded. */
5455
}
5556

5657
static void _breakiterator_move_forward(zend_object_iterator *iter)
@@ -79,8 +80,18 @@ static void _breakiterator_rewind(zend_object_iterator *iter)
7980
ZVAL_LONG(&zoi_iter->current, (zend_long)pos);
8081
}
8182

83+
static void zoi_with_current_dtor_self(zend_object_iterator *iter)
84+
{
85+
// Note: wrapping_obj is unused, call to zoi_with_current_dtor() not necessary
86+
zoi_with_current *zoi_iter = (zoi_with_current*)iter;
87+
ZEND_ASSERT(Z_ISUNDEF(zoi_iter->wrapping_obj));
88+
89+
// Unlike the other iterators, this iterator is a new, standalone instance
90+
zoi_iter->destroy_it(iter);
91+
}
92+
8293
static const zend_object_iterator_funcs breakiterator_iterator_funcs = {
83-
zoi_with_current_dtor,
94+
zoi_with_current_dtor_self,
8495
zoi_with_current_valid,
8596
zoi_with_current_get_current_data,
8697
NULL,
@@ -133,6 +144,7 @@ typedef struct zoi_break_iter_parts {
133144
static void _breakiterator_parts_destroy_it(zend_object_iterator *iter)
134145
{
135146
zval_ptr_dtor(&iter->data);
147+
efree(iter);
136148
}
137149

138150
static void _breakiterator_parts_get_current_key(zend_object_iterator *iter, zval *key)
@@ -231,7 +243,7 @@ void IntlIterator_from_BreakIterator_parts(zval *break_iter_zv,
231243
ii->iterator->index = 0;
232244

233245
((zoi_with_current*)ii->iterator)->destroy_it = _breakiterator_parts_destroy_it;
234-
ZVAL_OBJ(&((zoi_with_current*)ii->iterator)->wrapping_obj, Z_OBJ_P(object));
246+
ZVAL_OBJ_COPY(&((zoi_with_current*)ii->iterator)->wrapping_obj, Z_OBJ_P(object));
235247
ZVAL_UNDEF(&((zoi_with_current*)ii->iterator)->current);
236248

237249
((zoi_break_iter_parts*)ii->iterator)->bio = Z_INTL_BREAKITERATOR_P(break_iter_zv);

ext/intl/common/common_enum.cpp

+4-23
Original file line numberDiff line numberDiff line change
@@ -35,25 +35,7 @@ zend_object_handlers IntlIterator_handlers;
3535
void zoi_with_current_dtor(zend_object_iterator *iter)
3636
{
3737
zoi_with_current *zoiwc = (zoi_with_current*)iter;
38-
39-
if (!Z_ISUNDEF(zoiwc->wrapping_obj)) {
40-
/* we have to copy the pointer because zoiwc->wrapping_obj may be
41-
* changed midway the execution of zval_ptr_dtor() */
42-
zval *zwo = &zoiwc->wrapping_obj;
43-
44-
/* object is still here, we can rely on it to call this again and
45-
* destroy this object */
46-
zval_ptr_dtor(zwo);
47-
} else {
48-
/* Object not here anymore (we've been called by the object free handler)
49-
* Note that the iterator wrapper objects (that also depend on this
50-
* structure) call this function earlier, in the destruction phase, which
51-
* precedes the object free phase. Therefore there's no risk on this
52-
* function being called by the iterator wrapper destructor function and
53-
* not finding the memory of this iterator allocated anymore. */
54-
iter->funcs->invalidate_current(iter);
55-
zoiwc->destroy_it(iter);
56-
}
38+
zval_ptr_dtor(&zoiwc->wrapping_obj);
5739
}
5840

5941
U_CFUNC int zoi_with_current_valid(zend_object_iterator *iter)
@@ -124,6 +106,7 @@ static void string_enum_rewind(zend_object_iterator *iter)
124106
static void string_enum_destroy_it(zend_object_iterator *iter)
125107
{
126108
delete (StringEnumeration*)Z_PTR(iter->data);
109+
efree(iter);
127110
}
128111

129112
static const zend_object_iterator_funcs string_enum_object_iterator_funcs = {
@@ -148,7 +131,7 @@ U_CFUNC void IntlIterator_from_StringEnumeration(StringEnumeration *se, zval *ob
148131
ii->iterator->funcs = &string_enum_object_iterator_funcs;
149132
ii->iterator->index = 0;
150133
((zoi_with_current*)ii->iterator)->destroy_it = string_enum_destroy_it;
151-
ZVAL_OBJ(&((zoi_with_current*)ii->iterator)->wrapping_obj, Z_OBJ_P(object));
134+
ZVAL_OBJ_COPY(&((zoi_with_current*)ii->iterator)->wrapping_obj, Z_OBJ_P(object));
152135
ZVAL_UNDEF(&((zoi_with_current*)ii->iterator)->current);
153136
}
154137

@@ -157,9 +140,7 @@ static void IntlIterator_objects_free(zend_object *object)
157140
IntlIterator_object *ii = php_intl_iterator_fetch_object(object);
158141

159142
if (ii->iterator) {
160-
zval *wrapping_objp = &((zoi_with_current*)ii->iterator)->wrapping_obj;
161-
ZVAL_UNDEF(wrapping_objp);
162-
zend_iterator_dtor(ii->iterator);
143+
((zoi_with_current*)ii->iterator)->destroy_it(ii->iterator);
163144
}
164145
intl_error_reset(INTLITERATOR_ERROR_P(ii));
165146

ext/intl/tests/gh11874.phpt

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
GH-11874 (intl causing segfault in docker images)
3+
--EXTENSIONS--
4+
intl
5+
--FILE--
6+
<?php
7+
8+
foreach(IntlCalendar::getKeywordValuesForLocale('calendar', 'fa_IR', true) as $id) {
9+
var_dump($id);
10+
}
11+
12+
$result = '';
13+
foreach(IntlTimeZone::createTimeZoneIDEnumeration(IntlTimeZone::TYPE_ANY) as $id) {
14+
$result .= $id;
15+
}
16+
17+
var_dump(strlen($result) > 0);
18+
echo "No crash\n";
19+
20+
?>
21+
--EXPECT--
22+
string(7) "persian"
23+
string(9) "gregorian"
24+
string(7) "islamic"
25+
string(13) "islamic-civil"
26+
string(12) "islamic-tbla"
27+
bool(true)
28+
No crash

0 commit comments

Comments
 (0)