[php-src] master: Fix GH-17384: Reject too large number_format decimals (#22435)

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-06-26T16:24:23+08:00

Commit: https://github.com/php/php-src/commit/9313e8f6c05a040fea7d8cdf1d9949c16896ae83
Raw diff: https://github.com/php/php-src/commit/9313e8f6c05a040fea7d8cdf1d9949c16896ae83.diff

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

Changed paths:
  A  ext/standard/tests/math/gh17384.phpt
  M  NEWS
  M  ext/standard/math.c
  M  ext/standard/tests/math/number_format_basiclong_64bit.phpt
  M  ext/standard/tests/math/number_format_decimals.phpt


Diff:

diff --git a/NEWS b/NEWS
index ce6e46a4c36b..1fe33f473af6 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 6550e965d177..1498a1efc0b2 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 000000000000..eff207a906a6
--- /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 fd709fc648f9..7a986efc1fae 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 @@ $precisions = array(
     -17,
     -19,
     -20,
-    PHP_INT_MIN,
 );
 
 foreach ($numbers as $number) {
@@ -54,7 +53,6 @@ foreach ($numbers as $number) {
 ... 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 @@ foreach ($numbers as $number) {
 ... 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 @@ foreach ($numbers as $number) {
 ... 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 @@ foreach ($numbers as $number) {
 ... 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 @@ foreach ($numbers as $number) {
 ... 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 @@ foreach ($numbers as $number) {
 ... 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 @@ foreach ($numbers as $number) {
 ... 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 @@ foreach ($numbers as $number) {
 ... 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 @@ foreach ($numbers as $number) {
 ... 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 @@ foreach ($numbers as $number) {
 ... 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 @@ foreach ($numbers as $number) {
 ... 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 @@ foreach ($numbers as $number) {
 ... 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 @@ foreach ($numbers as $number) {
 ... 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 @@ foreach ($numbers as $number) {
 ... 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 @@ foreach ($numbers as $number) {
 ... 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 @@ foreach ($numbers as $number) {
 ... 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 @@ foreach ($numbers as $number) {
 ... 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 5fa45fddce2c..b589f0aefeba 100644
--- a/ext/standard/tests/math/number_format_decimals.phpt
+++ b/ext/standard/tests/math/number_format_decimals.phpt
@@ -29,7 +29,7 @@ $values = array(
     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 @@ testing float(1.5151)
 ... 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 @@ testing float(15.151)
 ... 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 @@ testing float(151.51)
 ... 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 @@ testing float(1515.1)
 ... 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 @@ testing int(15151)
 ... 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 @@ testing float(-1.5151)
 ... 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 @@ testing float(-15.151)
 ... 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 @@ testing float(-151.51)
 ... 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 @@ testing float(-1515.1)
 ... 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 @@ testing int(-15151)
 ... 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 @@ testing int(999)
 ... 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 @@ testing int(-999)
 ... 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 @@ testing float(999)
 ... 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 @@ testing float(-999)
 ... 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 @@ testing int(999999)
 ... 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 @@ testing int(-999999)
 ... 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 @@ testing float(999999)
 ... 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 @@ testing float(-999999)
 ... 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 @@ testing int(2147483647)
 ... 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 @@ testing int(-2147483648)
 ... 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"
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.