[php-src] master: Merge branch '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:16:18-04:00

Commit: https://github.com/php/php-src/commit/25466f7de0293ee626d3494d4ccc91a31644a0a0
Raw diff: https://github.com/php/php-src/commit/25466f7de0293ee626d3494d4ccc91a31644a0a0.diff

Merge branch 'PHP-8.5'

* PHP-8.5:
  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 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--
+<?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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.