[php-src] master: ext/intl: Fix NumberFormatter parse offset overflow (#22572)

Weilin Du via GitHub <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Weilin Du (LamentXU123)
Committer: GitHub (web-flow)
Pusher: LamentXU123
Date: 2026-07-04T02:03:08+08:00

Commit: https://github.com/php/php-src/commit/154881aa4ecaded7bee07b07debf108da061e8a3
Raw diff: https://github.com/php/php-src/commit/154881aa4ecaded7bee07b07debf108da061e8a3.diff

ext/intl: Fix NumberFormatter parse offset overflow (#22572)

Fixed NumberFormatter::parse() and NumberFormatter::parseCurrency() to
reject offset values outside the 32-bit range instead of silently
truncating them.

Changed paths:
  A  ext/intl/tests/formatter_parse_offset_overflow.phpt
  M  NEWS
  M  ext/intl/formatter/formatter_parse.cpp


Diff:

diff --git a/NEWS b/NEWS
index 3ea51b89f234..61a49cc1750a 100644
--- a/NEWS
+++ b/NEWS
@@ -10,6 +10,9 @@ PHP                                                                        NEWS
     warning when an IFD is not followed by a next-IFD offset). (Eyüp Can Akman)
 
 - Intl:
+  . Fixed NumberFormatter::parse() and NumberFormatter::parseCurrency() to
+    reject offset values outside the 32-bit range instead of silently
+    truncating them. (Weilin Du)
   . IntlDateFormatter::parse()/datefmt_parse() and
     IntlDateFormatter::localtime()/datefmt_localtime() now raise TypeError
     when the offset argument is not of type int. (Weilin Du)
diff --git a/ext/intl/formatter/formatter_parse.cpp b/ext/intl/formatter/formatter_parse.cpp
index a475960809b8..63c0fba4329f 100644
--- a/ext/intl/formatter/formatter_parse.cpp
+++ b/ext/intl/formatter/formatter_parse.cpp
@@ -50,7 +50,12 @@ U_CFUNC PHP_FUNCTION( numfmt_parse )
 	}
 
 	if (zposition) {
-		position = (int32_t) zval_get_long(zposition);
+		zend_long long_position = zval_get_long(zposition);
+		if (long_position < INT32_MIN || long_position > INT32_MAX) {
+			zend_argument_value_error(hasThis() ? 3 : 4, "must be between %d and %d", INT32_MIN, INT32_MAX);
+			RETURN_THROWS();
+		}
+		position = (int32_t) long_position;
 	}
 
 	/* Fetch the object. */
@@ -155,8 +160,13 @@ U_CFUNC PHP_FUNCTION( numfmt_parse_currency )
 	intl_stringFromChar(ustr, str, str_len, &INTL_DATA_ERROR_CODE(nfo));
 	INTL_METHOD_CHECK_STATUS( nfo, "String conversion to UTF-16 failed" );
 
-	if(zposition) {
-		position = (int32_t) zval_get_long(zposition);
+	if (zposition) {
+		zend_long long_position = zval_get_long(zposition);
+		if (long_position < INT32_MIN || long_position > INT32_MAX) {
+			zend_argument_value_error(hasThis() ? 3 : 4, "must be between %d and %d", INT32_MIN, INT32_MAX);
+			RETURN_THROWS();
+		}
+		position = (int32_t) long_position;
 	}
 
 	icu::ParsePosition pp(position);
diff --git a/ext/intl/tests/formatter_parse_offset_overflow.phpt b/ext/intl/tests/formatter_parse_offset_overflow.phpt
new file mode 100644
index 000000000000..9421c8897511
--- /dev/null
+++ b/ext/intl/tests/formatter_parse_offset_overflow.phpt
@@ -0,0 +1,46 @@
+--TEST--
+NumberFormatter parse offset overflow
+--EXTENSIONS--
+intl
+--SKIPIF--
+<?php if (PHP_INT_SIZE != 8) die("skip: 64-bit only"); ?>
+--FILE--
+<?php
+$fmt = new NumberFormatter('en_US', NumberFormatter::DECIMAL);
+$currencyFmt = new NumberFormatter('en_US', NumberFormatter::CURRENCY);
+
+function print_error(callable $callback): void {
+    try {
+        $callback();
+    } catch (Throwable $e) {
+        echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+    }
+}
+
+$offset = PHP_INT_MAX;
+print_error(function () use ($fmt, &$offset) {
+    $fmt->parse('123', NumberFormatter::TYPE_DOUBLE, $offset);
+});
+
+$offset = PHP_INT_MAX;
+print_error(function () use ($fmt, &$offset) {
+    numfmt_parse($fmt, '123', NumberFormatter::TYPE_DOUBLE, $offset);
+});
+
+$currency = '';
+$offset = PHP_INT_MAX;
+print_error(function () use ($currencyFmt, &$currency, &$offset) {
+    $currencyFmt->parseCurrency('$123.00', $currency, $offset);
+});
+
+$currency = '';
+$offset = PHP_INT_MAX;
+print_error(function () use ($currencyFmt, &$currency, &$offset) {
+    numfmt_parse_currency($currencyFmt, '$123.00', $currency, $offset);
+});
+?>
+--EXPECT--
+ValueError: NumberFormatter::parse(): Argument #3 ($offset) must be between -2147483648 and 2147483647
+ValueError: numfmt_parse(): Argument #4 ($offset) must be between -2147483648 and 2147483647
+ValueError: NumberFormatter::parseCurrency(): Argument #3 ($offset) must be between -2147483648 and 2147483647
+ValueError: numfmt_parse_currency(): Argument #4 ($offset) must be between -2147483648 and 2147483647
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.