Commit 14a7eadfb25 for php.net

commit 14a7eadfb25508af19ea831e51e66dc32a7b871e
Author: Weilin Du <weilindu@php.net>
Date:   Fri Jul 3 18:12:50 2026 +0800

    ext/intl: Fix NumberFormatter parse offset overflow

diff --git a/ext/intl/formatter/formatter_parse.cpp b/ext/intl/formatter/formatter_parse.cpp
index a475960809b..7aa92488704 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 (ZEND_LONG_EXCEEDS_INT(long_position)) {
+			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 (ZEND_LONG_EXCEEDS_INT(long_position)) {
+			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 00000000000..9421c889751
--- /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