Commit 9313e8f6c05 for php.net

commit 9313e8f6c05a040fea7d8cdf1d9949c16896ae83
Author: Weilin Du <weilindu@php.net>
Date:   Fri Jun 26 16:24:23 2026 +0800

    Fix GH-17384: Reject too large number_format decimals (#22435)

    number_format($number, 9876543210); is now silently equals to
    number_format($number, 2147483647); and generates 2147483647
    decimal places and eat up 2 GB memory (and exhaust almost half
    of them which cause a fatal error).

    I only reject very large positive numbers here (as every input larger
    than 2147483647 is silently turned into 2147483647). Because
    negative ones is always returning 0 anyways and only very large
    positive numbers can cause to such problems.

    Fixes #17384

diff --git a/NEWS b/NEWS
index ce6e46a4c36..1fe33f473af 100644
--- a/NEWS
+++ b/NEWS
@@ -283,6 +283,8 @@ PHP                                                                        NEWS
     (sebastian)
   . Fixed bug GH-22171 (Invalid auth header generation in http(s) stream
     wrapper). (David Carlier)
+  . Fixed bug GH-17384 (number_format() may exhaust memory with decimals
+    outside the range from -2147483648 to 2147483647). (Weilin Du)

 - Streams:
   . Added new stream errors API including new StreamException, StreamError
diff --git a/ext/standard/math.c b/ext/standard/math.c
index 6550e965d17..1498a1efc0b 100644
--- a/ext/standard/math.c
+++ b/ext/standard/math.c
@@ -1415,6 +1415,11 @@ PHP_FUNCTION(number_format)
 		thousand_sep_len = 1;
 	}

+	if (UNEXPECTED(dec > INT_MAX || dec < INT_MIN)) {
+		zend_argument_value_error(2, "must be between %d and %d", INT_MIN, INT_MAX);
+		RETURN_THROWS();
+	}
+
 	switch (Z_TYPE_P(num)) {
 		case IS_LONG:
 			RETURN_STR(_php_math_number_format_long(Z_LVAL_P(num), dec, dec_point, dec_point_len, thousand_sep, thousand_sep_len));
@@ -1429,11 +1434,7 @@ PHP_FUNCTION(number_format)
 				RETURN_STR(_php_math_number_format_long((zend_long)Z_DVAL_P(num), dec, dec_point, dec_point_len, thousand_sep, thousand_sep_len));
 			}

-			if (dec >= 0) {
-				dec_int = ZEND_LONG_INT_OVFL(dec) ? INT_MAX : (int)dec;
-			} else {
-				dec_int = ZEND_LONG_INT_UDFL(dec) ? INT_MIN : (int)dec;
-			}
+			dec_int = (int) dec;
 			RETURN_STR(_php_math_number_format_ex(Z_DVAL_P(num), dec_int, dec_point, dec_point_len, thousand_sep, thousand_sep_len));

 		default: ZEND_UNREACHABLE();
diff --git a/ext/standard/tests/math/gh17384.phpt b/ext/standard/tests/math/gh17384.phpt
new file mode 100644
index 00000000000..eff207a906a
--- /dev/null
+++ b/ext/standard/tests/math/gh17384.phpt
@@ -0,0 +1,25 @@
+--TEST--
+GH-17384: number_format() must reject decimals outside the int range
+--SKIPIF--
+<?php
+if (PHP_INT_SIZE !== 8) die('skip only for 64-bit');
+?>
+--FILE--
+<?php
+
+foreach ([1.23456, 1] as $number) {
+    foreach ([9876543210, -9876543210] as $decimals) {
+        try {
+            number_format($number, $decimals);
+        } catch (ValueError $exception) {
+            echo $exception->getMessage(), "\n";
+        }
+    }
+}
+
+?>
+--EXPECT--
+number_format(): Argument #2 ($decimals) must be between -2147483648 and 2147483647
+number_format(): Argument #2 ($decimals) must be between -2147483648 and 2147483647
+number_format(): Argument #2 ($decimals) must be between -2147483648 and 2147483647
+number_format(): Argument #2 ($decimals) must be between -2147483648 and 2147483647
diff --git a/ext/standard/tests/math/number_format_basiclong_64bit.phpt b/ext/standard/tests/math/number_format_basiclong_64bit.phpt
index fd709fc648f..7a986efc1fa 100644
--- a/ext/standard/tests/math/number_format_basiclong_64bit.phpt
+++ b/ext/standard/tests/math/number_format_basiclong_64bit.phpt
@@ -30,7 +30,6 @@
     -17,
     -19,
     -20,
-    PHP_INT_MIN,
 );

 foreach ($numbers as $number) {
@@ -54,7 +53,6 @@
 ... with precision -17: string(25) "9,200,000,000,000,000,000"
 ... with precision -19: string(26) "10,000,000,000,000,000,000"
 ... with precision -20: string(1) "0"
-... with precision -9223372036854775808: string(1) "0"
 --- testing: int(-9223372036854775808)
 ... with precision 5: string(32) "-9,223,372,036,854,775,808.00000"
 ... with precision 0: string(26) "-9,223,372,036,854,775,808"
@@ -65,7 +63,6 @@
 ... with precision -17: string(26) "-9,200,000,000,000,000,000"
 ... with precision -19: string(27) "-10,000,000,000,000,000,000"
 ... with precision -20: string(1) "0"
-... with precision -9223372036854775808: string(1) "0"
 --- testing: int(2147483647)
 ... with precision 5: string(19) "2,147,483,647.00000"
 ... with precision 0: string(13) "2,147,483,647"
@@ -76,7 +73,6 @@
 ... with precision -17: string(1) "0"
 ... with precision -19: string(1) "0"
 ... with precision -20: string(1) "0"
-... with precision -9223372036854775808: string(1) "0"
 --- testing: int(-2147483648)
 ... with precision 5: string(20) "-2,147,483,648.00000"
 ... with precision 0: string(14) "-2,147,483,648"
@@ -87,7 +83,6 @@
 ... with precision -17: string(1) "0"
 ... with precision -19: string(1) "0"
 ... with precision -20: string(1) "0"
-... with precision -9223372036854775808: string(1) "0"
 --- testing: int(9223372034707292160)
 ... with precision 5: string(31) "9,223,372,034,707,292,160.00000"
 ... with precision 0: string(25) "9,223,372,034,707,292,160"
@@ -98,7 +93,6 @@
 ... with precision -17: string(25) "9,200,000,000,000,000,000"
 ... with precision -19: string(26) "10,000,000,000,000,000,000"
 ... with precision -20: string(1) "0"
-... with precision -9223372036854775808: string(1) "0"
 --- testing: int(-9223372034707292160)
 ... with precision 5: string(32) "-9,223,372,034,707,292,160.00000"
 ... with precision 0: string(26) "-9,223,372,034,707,292,160"
@@ -109,7 +103,6 @@
 ... with precision -17: string(26) "-9,200,000,000,000,000,000"
 ... with precision -19: string(27) "-10,000,000,000,000,000,000"
 ... with precision -20: string(1) "0"
-... with precision -9223372036854775808: string(1) "0"
 --- testing: int(2147483648)
 ... with precision 5: string(19) "2,147,483,648.00000"
 ... with precision 0: string(13) "2,147,483,648"
@@ -120,7 +113,6 @@
 ... with precision -17: string(1) "0"
 ... with precision -19: string(1) "0"
 ... with precision -20: string(1) "0"
-... with precision -9223372036854775808: string(1) "0"
 --- testing: int(-2147483649)
 ... with precision 5: string(20) "-2,147,483,649.00000"
 ... with precision 0: string(14) "-2,147,483,649"
@@ -131,7 +123,6 @@
 ... with precision -17: string(1) "0"
 ... with precision -19: string(1) "0"
 ... with precision -20: string(1) "0"
-... with precision -9223372036854775808: string(1) "0"
 --- testing: int(4294967294)
 ... with precision 5: string(19) "4,294,967,294.00000"
 ... with precision 0: string(13) "4,294,967,294"
@@ -142,7 +133,6 @@
 ... with precision -17: string(1) "0"
 ... with precision -19: string(1) "0"
 ... with precision -20: string(1) "0"
-... with precision -9223372036854775808: string(1) "0"
 --- testing: int(4294967295)
 ... with precision 5: string(19) "4,294,967,295.00000"
 ... with precision 0: string(13) "4,294,967,295"
@@ -153,7 +143,6 @@
 ... with precision -17: string(1) "0"
 ... with precision -19: string(1) "0"
 ... with precision -20: string(1) "0"
-... with precision -9223372036854775808: string(1) "0"
 --- testing: int(4294967293)
 ... with precision 5: string(19) "4,294,967,293.00000"
 ... with precision 0: string(13) "4,294,967,293"
@@ -164,7 +153,6 @@
 ... with precision -17: string(1) "0"
 ... with precision -19: string(1) "0"
 ... with precision -20: string(1) "0"
-... with precision -9223372036854775808: string(1) "0"
 --- testing: int(9223372036854775806)
 ... with precision 5: string(31) "9,223,372,036,854,775,806.00000"
 ... with precision 0: string(25) "9,223,372,036,854,775,806"
@@ -175,7 +163,6 @@
 ... with precision -17: string(25) "9,200,000,000,000,000,000"
 ... with precision -19: string(26) "10,000,000,000,000,000,000"
 ... with precision -20: string(1) "0"
-... with precision -9223372036854775808: string(1) "0"
 --- testing: float(9.223372036854776E+18)
 ... with precision 5: string(31) "9,223,372,036,854,775,808.00000"
 ... with precision 0: string(25) "9,223,372,036,854,775,808"
@@ -186,7 +173,6 @@
 ... with precision -17: string(25) "9,200,000,000,000,000,000"
 ... with precision -19: string(26) "10,000,000,000,000,000,000"
 ... with precision -20: string(1) "0"
-... with precision -9223372036854775808: string(1) "0"
 --- testing: int(-9223372036854775807)
 ... with precision 5: string(32) "-9,223,372,036,854,775,807.00000"
 ... with precision 0: string(26) "-9,223,372,036,854,775,807"
@@ -197,7 +183,6 @@
 ... with precision -17: string(26) "-9,200,000,000,000,000,000"
 ... with precision -19: string(27) "-10,000,000,000,000,000,000"
 ... with precision -20: string(1) "0"
-... with precision -9223372036854775808: string(1) "0"
 --- testing: float(-9.223372036854776E+18)
 ... with precision 5: string(32) "-9,223,372,036,854,775,808.00000"
 ... with precision 0: string(26) "-9,223,372,036,854,775,808"
@@ -208,7 +193,6 @@
 ... with precision -17: string(26) "-9,200,000,000,000,000,000"
 ... with precision -19: string(27) "-10,000,000,000,000,000,000"
 ... with precision -20: string(1) "0"
-... with precision -9223372036854775808: string(1) "0"
 --- testing: float(9.223372036854775E+18)
 ... with precision 5: string(31) "9,223,372,036,854,774,784.00000"
 ... with precision 0: string(25) "9,223,372,036,854,774,784"
@@ -219,7 +203,6 @@
 ... with precision -17: string(25) "9,200,000,000,000,000,000"
 ... with precision -19: string(26) "10,000,000,000,000,000,000"
 ... with precision -20: string(1) "0"
-... with precision -9223372036854775808: string(1) "0"
 --- testing: float(-9.223372036854775E+18)
 ... with precision 5: string(32) "-9,223,372,036,854,774,784.00000"
 ... with precision 0: string(26) "-9,223,372,036,854,774,784"
@@ -230,4 +213,3 @@
 ... with precision -17: string(26) "-9,200,000,000,000,000,000"
 ... with precision -19: string(27) "-10,000,000,000,000,000,000"
 ... with precision -20: string(1) "0"
-... with precision -9223372036854775808: string(1) "0"
diff --git a/ext/standard/tests/math/number_format_decimals.phpt b/ext/standard/tests/math/number_format_decimals.phpt
index 5fa45fddce2..b589f0aefeb 100644
--- a/ext/standard/tests/math/number_format_decimals.phpt
+++ b/ext/standard/tests/math/number_format_decimals.phpt
@@ -29,7 +29,7 @@
     MIN_INT32,
 );

-$decimals = array(0, 1, 2, 3, 4, 5, -1, -2, -3, -4, -5, PHP_INT_MIN);
+$decimals = array(0, 1, 2, 3, 4, 5, -1, -2, -3, -4, -5);

 foreach ($values as $value) {
     echo 'testing ';
@@ -55,7 +55,6 @@
 ... with decimal places of -3: string(1) "0"
 ... with decimal places of -4: string(1) "0"
 ... with decimal places of -5: string(1) "0"
-... with decimal places of %i: string(1) "0"
 testing float(15.151)
 ... with decimal places of 0: string(2) "15"
 ... with decimal places of 1: string(4) "15.2"
@@ -68,7 +67,6 @@
 ... with decimal places of -3: string(1) "0"
 ... with decimal places of -4: string(1) "0"
 ... with decimal places of -5: string(1) "0"
-... with decimal places of %i: string(1) "0"
 testing float(151.51)
 ... with decimal places of 0: string(3) "152"
 ... with decimal places of 1: string(5) "151.5"
@@ -81,7 +79,6 @@
 ... with decimal places of -3: string(1) "0"
 ... with decimal places of -4: string(1) "0"
 ... with decimal places of -5: string(1) "0"
-... with decimal places of %i: string(1) "0"
 testing float(1515.1)
 ... with decimal places of 0: string(5) "1,515"
 ... with decimal places of 1: string(7) "1,515.1"
@@ -94,7 +91,6 @@
 ... with decimal places of -3: string(5) "2,000"
 ... with decimal places of -4: string(1) "0"
 ... with decimal places of -5: string(1) "0"
-... with decimal places of %i: string(1) "0"
 testing int(15151)
 ... with decimal places of 0: string(6) "15,151"
 ... with decimal places of 1: string(8) "15,151.0"
@@ -107,7 +103,6 @@
 ... with decimal places of -3: string(6) "15,000"
 ... with decimal places of -4: string(6) "20,000"
 ... with decimal places of -5: string(1) "0"
-... with decimal places of %i: string(1) "0"
 testing float(-1.5151)
 ... with decimal places of 0: string(2) "-2"
 ... with decimal places of 1: string(4) "-1.5"
@@ -120,7 +115,6 @@
 ... with decimal places of -3: string(1) "0"
 ... with decimal places of -4: string(1) "0"
 ... with decimal places of -5: string(1) "0"
-... with decimal places of %i: string(1) "0"
 testing float(-15.151)
 ... with decimal places of 0: string(3) "-15"
 ... with decimal places of 1: string(5) "-15.2"
@@ -133,7 +127,6 @@
 ... with decimal places of -3: string(1) "0"
 ... with decimal places of -4: string(1) "0"
 ... with decimal places of -5: string(1) "0"
-... with decimal places of %i: string(1) "0"
 testing float(-151.51)
 ... with decimal places of 0: string(4) "-152"
 ... with decimal places of 1: string(6) "-151.5"
@@ -146,7 +139,6 @@
 ... with decimal places of -3: string(1) "0"
 ... with decimal places of -4: string(1) "0"
 ... with decimal places of -5: string(1) "0"
-... with decimal places of %i: string(1) "0"
 testing float(-1515.1)
 ... with decimal places of 0: string(6) "-1,515"
 ... with decimal places of 1: string(8) "-1,515.1"
@@ -159,7 +151,6 @@
 ... with decimal places of -3: string(6) "-2,000"
 ... with decimal places of -4: string(1) "0"
 ... with decimal places of -5: string(1) "0"
-... with decimal places of %i: string(1) "0"
 testing int(-15151)
 ... with decimal places of 0: string(7) "-15,151"
 ... with decimal places of 1: string(9) "-15,151.0"
@@ -172,7 +163,6 @@
 ... with decimal places of -3: string(7) "-15,000"
 ... with decimal places of -4: string(7) "-20,000"
 ... with decimal places of -5: string(1) "0"
-... with decimal places of %i: string(1) "0"
 testing int(999)
 ... with decimal places of 0: string(3) "999"
 ... with decimal places of 1: string(5) "999.0"
@@ -185,7 +175,6 @@
 ... with decimal places of -3: string(5) "1,000"
 ... with decimal places of -4: string(1) "0"
 ... with decimal places of -5: string(1) "0"
-... with decimal places of %i: string(1) "0"
 testing int(-999)
 ... with decimal places of 0: string(4) "-999"
 ... with decimal places of 1: string(6) "-999.0"
@@ -198,7 +187,6 @@
 ... with decimal places of -3: string(6) "-1,000"
 ... with decimal places of -4: string(1) "0"
 ... with decimal places of -5: string(1) "0"
-... with decimal places of %i: string(1) "0"
 testing float(999)
 ... with decimal places of 0: string(3) "999"
 ... with decimal places of 1: string(5) "999.0"
@@ -211,7 +199,6 @@
 ... with decimal places of -3: string(5) "1,000"
 ... with decimal places of -4: string(1) "0"
 ... with decimal places of -5: string(1) "0"
-... with decimal places of %i: string(1) "0"
 testing float(-999)
 ... with decimal places of 0: string(4) "-999"
 ... with decimal places of 1: string(6) "-999.0"
@@ -224,7 +211,6 @@
 ... with decimal places of -3: string(6) "-1,000"
 ... with decimal places of -4: string(1) "0"
 ... with decimal places of -5: string(1) "0"
-... with decimal places of %i: string(1) "0"
 testing int(999999)
 ... with decimal places of 0: string(7) "999,999"
 ... with decimal places of 1: string(9) "999,999.0"
@@ -237,7 +223,6 @@
 ... with decimal places of -3: string(9) "1,000,000"
 ... with decimal places of -4: string(9) "1,000,000"
 ... with decimal places of -5: string(9) "1,000,000"
-... with decimal places of %i: string(1) "0"
 testing int(-999999)
 ... with decimal places of 0: string(8) "-999,999"
 ... with decimal places of 1: string(10) "-999,999.0"
@@ -250,7 +235,6 @@
 ... with decimal places of -3: string(10) "-1,000,000"
 ... with decimal places of -4: string(10) "-1,000,000"
 ... with decimal places of -5: string(10) "-1,000,000"
-... with decimal places of %i: string(1) "0"
 testing float(999999)
 ... with decimal places of 0: string(7) "999,999"
 ... with decimal places of 1: string(9) "999,999.0"
@@ -263,7 +247,6 @@
 ... with decimal places of -3: string(9) "1,000,000"
 ... with decimal places of -4: string(9) "1,000,000"
 ... with decimal places of -5: string(9) "1,000,000"
-... with decimal places of %i: string(1) "0"
 testing float(-999999)
 ... with decimal places of 0: string(8) "-999,999"
 ... with decimal places of 1: string(10) "-999,999.0"
@@ -276,7 +259,6 @@
 ... with decimal places of -3: string(10) "-1,000,000"
 ... with decimal places of -4: string(10) "-1,000,000"
 ... with decimal places of -5: string(10) "-1,000,000"
-... with decimal places of %i: string(1) "0"
 testing int(2147483647)
 ... with decimal places of 0: string(13) "2,147,483,647"
 ... with decimal places of 1: string(15) "2,147,483,647.0"
@@ -289,7 +271,6 @@
 ... with decimal places of -3: string(13) "2,147,484,000"
 ... with decimal places of -4: string(13) "2,147,480,000"
 ... with decimal places of -5: string(13) "2,147,500,000"
-... with decimal places of %i: string(1) "0"
 testing int(-2147483648)
 ... with decimal places of 0: string(14) "-2,147,483,648"
 ... with decimal places of 1: string(16) "-2,147,483,648.0"
@@ -302,4 +283,3 @@
 ... with decimal places of -3: string(14) "-2,147,484,000"
 ... with decimal places of -4: string(14) "-2,147,480,000"
 ... with decimal places of -5: string(14) "-2,147,500,000"
-... with decimal places of %i: string(1) "0"