From 718535c4177b402aa2532290101a48d9f1c0455e Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Wed, 24 Jun 2026 20:05:13 +0800 Subject: [PATCH 1/3] Fix GH-17384: Reject too large number_format decimals --- NEWS | 2 ++ ext/standard/math.c | 11 ++++++----- ext/standard/tests/math/gh17384.phpt | 24 ++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 ext/standard/tests/math/gh17384.phpt 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..99562abbbd89 100644 --- a/ext/standard/math.c +++ b/ext/standard/math.c @@ -1415,6 +1415,11 @@ PHP_FUNCTION(number_format) thousand_sep_len = 1; } + if (dec > INT_MAX) { + zend_argument_value_error(2, "must be less than or equal to %d", 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 = ZEND_LONG_INT_UDFL(dec) ? INT_MIN : (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..c6cd2231b507 --- /dev/null +++ b/ext/standard/tests/math/gh17384.phpt @@ -0,0 +1,24 @@ +--TEST-- +GH-17384: number_format() must reject too large decimals +--SKIPIF-- + +--FILE-- +getMessage(), "\n"; + } +} + +var_dump(number_format(1.23456, -9876543210)); + +?> +--EXPECT-- +number_format(): Argument #2 ($decimals) must be less than or equal to 2147483647 +number_format(): Argument #2 ($decimals) must be less than or equal to 2147483647 +string(1) "0" From c554ab561cc2283297c318c00b07b9580ce1d1bc Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Thu, 25 Jun 2026 22:35:36 +0800 Subject: [PATCH 2/3] Add restrictions for negative large numbers --- ext/standard/math.c | 6 +++--- ext/standard/tests/math/gh17384.phpt | 21 +++++++++++---------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/ext/standard/math.c b/ext/standard/math.c index 99562abbbd89..1498a1efc0b2 100644 --- a/ext/standard/math.c +++ b/ext/standard/math.c @@ -1415,8 +1415,8 @@ PHP_FUNCTION(number_format) thousand_sep_len = 1; } - if (dec > INT_MAX) { - zend_argument_value_error(2, "must be less than or equal to %d", INT_MAX); + 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(); } @@ -1434,7 +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)); } - 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 index c6cd2231b507..eff207a906a6 100644 --- a/ext/standard/tests/math/gh17384.phpt +++ b/ext/standard/tests/math/gh17384.phpt @@ -1,5 +1,5 @@ --TEST-- -GH-17384: number_format() must reject too large decimals +GH-17384: number_format() must reject decimals outside the int range --SKIPIF-- getMessage(), "\n"; + foreach ([9876543210, -9876543210] as $decimals) { + try { + number_format($number, $decimals); + } catch (ValueError $exception) { + echo $exception->getMessage(), "\n"; + } } } -var_dump(number_format(1.23456, -9876543210)); - ?> --EXPECT-- -number_format(): Argument #2 ($decimals) must be less than or equal to 2147483647 -number_format(): Argument #2 ($decimals) must be less than or equal to 2147483647 -string(1) "0" +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 From c02f2915a3948eedfba202251e45802ff53ac5b7 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Thu, 25 Jun 2026 23:49:00 +0800 Subject: [PATCH 3/3] fix CI and better NEWS file --- .../math/number_format_basiclong_64bit.phpt | 18 --------------- .../tests/math/number_format_decimals.phpt | 22 +------------------ 2 files changed, 1 insertion(+), 39 deletions(-) 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"