Commit 70603b9465f for php.net

commit 70603b9465f52a33bc5bb51ed4580a9f5ed56b34
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Mon Aug 17 06:55:19 2026 -0400

    Stop grapheme_strrev from using UBRK_DONE as a byte index (#23323)

    ubrk_previous() returns UBRK_DONE after the first boundary. The loop
    condition ran before that assignment, so the body treated -1 as an
    offset and wrote into the zend_string NUL. Break when the iterator is
    done, and NUL-terminate the result of zend_string_alloc.

diff --git a/NEWS b/NEWS
index a0c4359f4a0..6550a7a9641 100644
--- a/NEWS
+++ b/NEWS
@@ -29,6 +29,10 @@ PHP                                                                        NEWS
     left busy for the next fetch, and rows delivered from a result another
     statement took over. (KentarouTakeda)

+- Intl:
+  . Fixed grapheme_strrev() treating UBRK_DONE as a byte index and leaving
+    the result without a terminating NUL. (iliaal)
+
 - Phar:
   . Fixed Phar archives being automatically detected when ".phar" only occurs
     in a directory name or is not a filename extension in an included file's
diff --git a/ext/intl/grapheme/grapheme_string.cpp b/ext/intl/grapheme/grapheme_string.cpp
index 5e614be6ae7..a1daae84db9 100644
--- a/ext/intl/grapheme/grapheme_string.cpp
+++ b/ext/intl/grapheme/grapheme_string.cpp
@@ -1175,6 +1175,9 @@ U_CFUNC PHP_FUNCTION(grapheme_strrev)
 	current = ZSTR_LEN(string);
 	for (end = pstr; pos != UBRK_DONE; ) {
 		pos = ubrk_previous(bi);
+		if (pos == UBRK_DONE) {
+			break;
+		}
 		end_len = current - pos;
 		for (int32_t j = 0; j < end_len; j++) {
 			*p++ = *(pstr + pos + j);
@@ -1182,6 +1185,7 @@ U_CFUNC PHP_FUNCTION(grapheme_strrev)
 		current = pos;
 	}
 ubrk_end:
+	ZSTR_VAL(ret)[ZSTR_LEN(ret)] = '\0';
 	RETVAL_NEW_STR(ret);
 	ubrk_close(bi);
 close:
diff --git a/ext/intl/tests/grapheme_strrev_ubrk_done.phpt b/ext/intl/tests/grapheme_strrev_ubrk_done.phpt
new file mode 100644
index 00000000000..6f70f7cd1f7
--- /dev/null
+++ b/ext/intl/tests/grapheme_strrev_ubrk_done.phpt
@@ -0,0 +1,25 @@
+--TEST--
+grapheme_strrev() stops at UBRK_DONE instead of using it as a byte index
+--EXTENSIONS--
+intl
+--FILE--
+<?php
+
+$cases = [
+    'abc',
+    'a',
+    '土下座',
+    "null\x00byte",
+];
+
+foreach ($cases as $s) {
+    $rev = grapheme_strrev($s);
+    echo strlen($s), ' ', strlen($rev), ' ', bin2hex($rev), "\n";
+}
+
+?>
+--EXPECT--
+3 3 636261
+1 1 61
+9 9 e5baa7e4b88be59c9f
+9 9 65747962006c6c756e