diff --git a/NEWS b/NEWS index 500f75cc050d..e112bfb5351e 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,8 @@ PHP NEWS results. (iliaal) . Fixed a leak in Locale::getKeywords() when a keyword value cannot be read. (iliaal) + . Fixed a use-after-free when IntlRuleBasedBreakIterator is constructed + from compiled rules. (iliaal) - PDO_PGSQL: . Added Pdo\Pgsql::ATTR_CHUNK_SIZE to fetch a result set in chunks of the diff --git a/ext/intl/breakiterator/breakiterator_class.cpp b/ext/intl/breakiterator/breakiterator_class.cpp index 4d5793696cbb..fe6eb2749ce6 100644 --- a/ext/intl/breakiterator/breakiterator_class.cpp +++ b/ext/intl/breakiterator/breakiterator_class.cpp @@ -109,6 +109,9 @@ static zend_object *BreakIterator_clone_obj(zend_object *object) } else { bio_new->biter = new_biter; ZVAL_COPY(&bio_new->text, &bio_orig->text); + if (bio_orig->compiled_rules) { + bio_new->compiled_rules = zend_string_copy(bio_orig->compiled_rules); + } } } else { zend_throw_error(NULL, "Cannot clone uninitialized BreakIterator"); @@ -163,6 +166,7 @@ static void breakiterator_object_init(BreakIterator_object *bio) { intl_error_init(BREAKITER_ERROR_P(bio)); bio->biter = NULL; + bio->compiled_rules = NULL; ZVAL_UNDEF(&bio->text); } /* }}} */ @@ -177,6 +181,10 @@ static void BreakIterator_objects_free(zend_object *object) delete bio->biter; bio->biter = NULL; } + if (bio->compiled_rules) { + zend_string_release(bio->compiled_rules); + bio->compiled_rules = NULL; + } intl_error_reset(BREAKITER_ERROR_P(bio)); zend_object_std_dtor(&bio->zo); diff --git a/ext/intl/breakiterator/breakiterator_class.h b/ext/intl/breakiterator/breakiterator_class.h index cb4f072139ec..38e83da64dae 100644 --- a/ext/intl/breakiterator/breakiterator_class.h +++ b/ext/intl/breakiterator/breakiterator_class.h @@ -38,6 +38,8 @@ typedef struct { // current text zval text; + zend_string *compiled_rules; + zend_object zo; } BreakIterator_object; diff --git a/ext/intl/breakiterator/rulebasedbreakiterator_methods.cpp b/ext/intl/breakiterator/rulebasedbreakiterator_methods.cpp index 4526b9faab13..611144b58ab6 100644 --- a/ext/intl/breakiterator/rulebasedbreakiterator_methods.cpp +++ b/ext/intl/breakiterator/rulebasedbreakiterator_methods.cpp @@ -87,6 +87,9 @@ U_CFUNC PHP_METHOD(IntlRuleBasedBreakIterator, __construct) } breakiterator_object_create(object, rbbi, false); + if (compiled) { + Z_INTL_BREAKITERATOR_P(object)->compiled_rules = zend_string_copy(rules); + } } U_CFUNC PHP_METHOD(IntlRuleBasedBreakIterator, getRules) diff --git a/ext/intl/tests/rbbiter_compiled_rules_lifetime.phpt b/ext/intl/tests/rbbiter_compiled_rules_lifetime.phpt new file mode 100644 index 000000000000..d0eb5fc2e755 --- /dev/null +++ b/ext/intl/tests/rbbiter_compiled_rules_lifetime.phpt @@ -0,0 +1,77 @@ +--TEST-- +IntlRuleBasedBreakIterator compiled rules outlive the source string +--EXTENSIONS-- +intl +--SKIPIF-- += 68.1'); ?> +--FILE-- +getBinaryRules()); + +$it = new IntlRuleBasedBreakIterator($src->getBinaryRules(), true); +unset($src); + +/* ICU aliases the buffer it was built from, so the freed rules have to be + reclaimed and overwritten for the iterator below to read stale bytes. */ +$ballast = []; +for ($i = 0; $i < 16; $i++) { + $ballast[] = str_repeat("\xCC", $len); +} + +$it->setText('ab,cd'); +echo $it->first(), "\n"; +while (true) { + $n = $it->next(); + if ($n === IntlBreakIterator::DONE) { + break; + } + echo $n, "\n"; +} + +$clone = clone $it; +unset($it); +$ballast[] = str_repeat("\xDD", $len); +$clone->setText('xy'); +echo $clone->first(), "\n"; +echo $clone->next(), "\n"; + +$src = new IntlRuleBasedBreakIterator($rules); +$it = new IntlRuleBasedBreakIterator($src->getBinaryRules(), true); +unset($src); +for ($i = 0; $i < 16; $i++) { + $ballast[] = str_repeat("\xEE", $len); +} +$it->setText('ab,cd'); +$parts = $it->getPartsIterator(); +unset($it); +foreach ($parts as $p) { + echo $p, "\n"; +} + +?> +--EXPECT-- +0 +2 +3 +5 +0 +2 +ab +, +cd