[php-src] master: Stop grapheme_strrev from using UBRK_DONE as a byte index (#23323)

Ilia Alshanetsky via GitHub <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Ilia Alshanetsky (iliaal)
Committer: GitHub (web-flow)
Pusher: iliaal
Date: 2026-08-17T06:55:19-04:00

Commit: https://github.com/php/php-src/commit/70603b9465f52a33bc5bb51ed4580a9f5ed56b34
Raw diff: https://github.com/php/php-src/commit/70603b9465f52a33bc5bb51ed4580a9f5ed56b34.diff

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.

Changed paths:
  A  ext/intl/tests/grapheme_strrev_ubrk_done.phpt
  M  NEWS
  M  ext/intl/grapheme/grapheme_string.cpp


Diff:

diff --git a/NEWS b/NEWS
index a0c4359f4a09..6550a7a9641d 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 5e614be6ae72..a1daae84db9c 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 000000000000..6f70f7cd1f7f
--- /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
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.