[php-src] master: Merge branch 'PHP-8.4' into PHP-8.5
Ilia Alshanetsky <[email protected]>
| Newsgroups | gmane.comp.php.cvs.general |
|---|---|
| Message-ID | <[email protected]> |
Author: Ilia Alshanetsky (iliaal)
Date: 2026-08-28T09:15:57-04:00
Commit: https://github.com/php/php-src/commit/3f5baab5a99c37c81f5578e551814346daa4c1f6
Raw diff: https://github.com/php/php-src/commit/3f5baab5a99c37c81f5578e551814346daa4c1f6.diff
Merge branch 'PHP-8.4' into PHP-8.5
* PHP-8.4:
Keep compiled RuleBasedBreakIterator rules alive for the iterator
Changed paths:
A ext/intl/tests/rbbiter_compiled_rules_lifetime.phpt
M NEWS
M ext/intl/breakiterator/breakiterator_class.cpp
M ext/intl/breakiterator/breakiterator_class.h
M ext/intl/breakiterator/rulebasedbreakiterator_methods.cpp
Diff:
diff --git a/NEWS b/NEWS
index dbce8fde3a0d..a5d2cfe2c037 100644
--- a/NEWS
+++ b/NEWS
@@ -37,6 +37,8 @@ PHP NEWS
. Fixed grapheme_str_split() treating UBRK_DONE as a byte index. (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)
- Opcache:
. Fixed opcache.protect_memory race under ZTS. (realFlowControl)
diff --git a/ext/intl/breakiterator/breakiterator_class.cpp b/ext/intl/breakiterator/breakiterator_class.cpp
index 4976d4ff675b..e078ff691274 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 0852d86e2a82..8061acc0ddac 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 a7c322b1d816..2d17e93e46da 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--
+<?php if (version_compare(INTL_ICU_VERSION, '68.1') < 0) die('skip for ICU >= 68.1'); ?>
+--FILE--
+<?php
+
+$rules = <<<RULES
+\$LN = [[:letter:] [:number:]];
+\$S = [.;,:];
+
+!!forward;
+\$LN+ {1};
+\$S+ {42};
+!!reverse;
+\$LN+ {1};
+\$S+ {42};
+!!safe_forward;
+!!safe_reverse;
+RULES;
+
+$src = new IntlRuleBasedBreakIterator($rules);
+$len = strlen($src->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