[PHP-CVS] [php-src] PHP-8.5: Bound getSingletonPos before reading the next separator (#23350)

[email protected] (Ilia Alshanetsky via Weilin Du)
Newsgroups php.cvs
Message-ID <[email protected]>
Author: Ilia Alshanetsky (iliaal)
Committer: Weilin Du (LamentXU123)
Date: 2026-08-18T22:38:26+08:00

Commit: https://github.com/php/php-src/commit/9f23a4de630864749da25da803080e090de93ab1
Raw diff: https://github.com/php/php-src/commit/9f23a4de630864749da25da803080e090de93ab1.diff

Bound getSingletonPos before reading the next separator (#23350)

getSingletonPos() read str[i+2] after the last '-' or '_' in a locale,
one byte past the NUL for strings such as "en-". Require i+2 to be inside
the string before testing it for another separator.

Output otherwise matches the unfixed parser unless that heap byte happens to
be another separator, which is why GH-22498 was previously closed.

Also audit getStrrtokenPos() and both callers, get_icu_value_internal() and
get_private_subtags(); their bounds handling is already safe.

Co-authored-by: Xuyang Zhang <[email protected]>
Closes #23350

Changed paths:
  A  ext/intl/tests/locale_parse_trailing_separator.phpt
  M  NEWS
  M  ext/intl/locale/locale_methods.c


Diff:

diff --git a/NEWS b/NEWS
index 5ab1602dbd69..4cafbbd5a900 100644
--- a/NEWS
+++ b/NEWS
@@ -15,6 +15,8 @@ PHP                                                                        NEWS
     the ICU constructor adopts the TimeZone. (iliaal)
   . Fixed bug GH-23094 (NumberFormatter parsing offsets use UTF-16 positions
     for UTF-8 strings). (ColumbusLabs)
+  . Fixed Locale::parseLocale() reading past a trailing '-' or '_'.
+    (iliaal, Xuyang Zhang)
 
 - Opcache:
   . Fixed opcache.protect_memory race under ZTS. (realFlowControl)
diff --git a/ext/intl/locale/locale_methods.c b/ext/intl/locale/locale_methods.c
index b5d48257338a..e3894b6f28fb 100644
--- a/ext/intl/locale/locale_methods.c
+++ b/ext/intl/locale/locale_methods.c
@@ -279,7 +279,7 @@ static zend_off_t getSingletonPos(const char* str)
 					break;
 				} else {
 					/* delimiter found; check for singleton */
-					if( isIDSeparator(*(str+i+2)) ){
+					if( (size_t)i + 2 < len && isIDSeparator(*(str+i+2)) ){
 						/* a singleton; so send the position of separator before singleton */
 						result = i+1;
 						break;
diff --git a/ext/intl/tests/locale_parse_trailing_separator.phpt b/ext/intl/tests/locale_parse_trailing_separator.phpt
new file mode 100644
index 000000000000..96b2139b72c3
--- /dev/null
+++ b/ext/intl/tests/locale_parse_trailing_separator.phpt
@@ -0,0 +1,45 @@
+--TEST--
+Locale::parseLocale() does not read past a trailing '-' or '_'
+--EXTENSIONS--
+intl
+--FILE--
+<?php
+/* Enough lengths that the byte past the end clears the allocation. */
+foreach (['-', '_'] as $sep) {
+    for ($len = 1; $len <= 64; $len++) {
+        Locale::parseLocale(str_repeat('a', $len) . $sep);
+    }
+}
+
+$locales = [
+    'en-',
+    'foo-',
+    'en_US-',
+    'en_',
+    'de-CH-x-',
+];
+
+foreach ($locales as $locale) {
+    echo $locale, ': ';
+    var_export(Locale::parseLocale($locale));
+    echo "\n";
+}
+?>
+--EXPECT--
+en-: array (
+  'language' => 'en',
+)
+foo-: array (
+  'language' => 'foo',
+)
+en_US-: array (
+  'language' => 'en',
+  'region' => 'US',
+)
+en_: array (
+  'language' => 'en',
+)
+de-CH-x-: array (
+  'language' => 'de',
+  'region' => 'CH',
+)
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.