From e549981f76c4d92d644a54754c15a19224326d61 Mon Sep 17 00:00:00 2001 From: Joseph Scott Date: Thu, 6 Aug 2026 09:10:30 -0600 Subject: [PATCH 1/3] Avoid fatal error condition in absint() https://core.trac.wordpress.org/ticket/65826 --- src/wp-includes/load.php | 9 ++++++++- tests/phpunit/tests/functions/absint.php | 13 +++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/load.php b/src/wp-includes/load.php index ff68c084104f1..8934d36032397 100644 --- a/src/wp-includes/load.php +++ b/src/wp-includes/load.php @@ -1467,7 +1467,14 @@ function is_multisite() { * @phpstan-return non-negative-int */ function absint( $maybeint ): int { - return abs( (int) $maybeint ); + $abs = abs( (int) $maybeint ); + + // abs() can return a float. + if ( is_float( $abs ) ) { + return PHP_INT_MAX; + } + + return $abs; } /** diff --git a/tests/phpunit/tests/functions/absint.php b/tests/phpunit/tests/functions/absint.php index 5a9edd6961077..c716c07045c26 100644 --- a/tests/phpunit/tests/functions/absint.php +++ b/tests/phpunit/tests/functions/absint.php @@ -11,6 +11,7 @@ class Tests_Functions_Absint extends WP_UnitTestCase { /** * @ticket 60101 + * @ticket 65826 * * @dataProvider data_absint */ @@ -76,6 +77,18 @@ public function data_absint() { 'test_value' => array( '99' ), 'expected_value' => 1, ), + 'PHP_INT_MIN int' => array( + 'test_value' => PHP_INT_MIN, + 'expected_value' => PHP_INT_MAX, + ), + 'PHP_INT_MIN string' => array( + 'test_value' => '-9223372036854775808', + 'expected_value' => PHP_INT_MAX, + ), + 'out of range negative' => array( + 'test_value' => '-99999999999999999999', + 'expected_value' => PHP_INT_MAX, + ), ); } } From d3b4f6b2d5ea2cec2facb8ab77173bc446a71a6e Mon Sep 17 00:00:00 2001 From: Joseph Scott Date: Thu, 6 Aug 2026 17:04:21 -0600 Subject: [PATCH 2/3] Catch more conditions With more tests too --- src/wp-includes/load.php | 27 ++++- tests/phpunit/tests/functions/absint.php | 120 ++++++++++++++++++++--- 2 files changed, 127 insertions(+), 20 deletions(-) diff --git a/src/wp-includes/load.php b/src/wp-includes/load.php index 8934d36032397..d82d05fa7a98e 100644 --- a/src/wp-includes/load.php +++ b/src/wp-includes/load.php @@ -1460,21 +1460,38 @@ function is_multisite() { /** * Converts a value to non-negative integer. * + * Values outside of the range of an integer are clamped to `PHP_INT_MAX`. + * * @since 2.5.0 + * @since 7.1.0 Out of range values are clamped to `PHP_INT_MAX` instead of + * being cast to an undefined value. * * @param mixed $maybeint Data you wish to have converted to a non-negative integer. * @return int A non-negative integer. * @phpstan-return non-negative-int */ function absint( $maybeint ): int { - $abs = abs( (int) $maybeint ); + /* + * Casting an out of range float to an integer is undefined, and raises a + * warning as of PHP 8.5. Numeric strings do not need this treatment, as + * casting those already clamps to PHP_INT_MAX or PHP_INT_MIN. + */ + if ( is_float( $maybeint ) ) { + if ( is_nan( $maybeint ) ) { + return 0; + } - // abs() can return a float. - if ( is_float( $abs ) ) { - return PHP_INT_MAX; + if ( abs( $maybeint ) >= (float) PHP_INT_MAX ) { + return PHP_INT_MAX; + } } - return $abs; + /* + * PHP_INT_MIN is the only integer whose absolute value is larger than + * PHP_INT_MAX, which would make abs() return a float. Clamping it to + * -PHP_INT_MAX first keeps the result within the integer range. + */ + return abs( max( (int) $maybeint, -PHP_INT_MAX ) ); } /** diff --git a/tests/phpunit/tests/functions/absint.php b/tests/phpunit/tests/functions/absint.php index c716c07045c26..53a3c7b92217f 100644 --- a/tests/phpunit/tests/functions/absint.php +++ b/tests/phpunit/tests/functions/absint.php @@ -29,66 +29,156 @@ public function test_absint( $test_value, $expected_value ) { */ public function data_absint() { return array( - '1 int' => array( + 'zero' => array( + 'test_value' => 0, + 'expected_value' => 0, + ), + '1 int' => array( 'test_value' => 1, 'expected_value' => 1, ), - '1 string' => array( + '1 string' => array( 'test_value' => '1', 'expected_value' => 1, ), - '-1 int' => array( + '-1 int' => array( 'test_value' => -1, 'expected_value' => 1, ), - '-1 string' => array( + '-1 string' => array( 'test_value' => '-1', 'expected_value' => 1, ), - '9.1 float' => array( + '9.1 float' => array( 'test_value' => 9.1, 'expected_value' => 9, ), - '9.9 float' => array( + '9.9 float' => array( 'test_value' => 9.9, 'expected_value' => 9, ), - 'string' => array( + 'string' => array( 'test_value' => 'string', 'expected_value' => 0, ), - 'string_1' => array( + 'string_1' => array( 'test_value' => 'string_1', 'expected_value' => 0, ), - '999_string' => array( + '999_string' => array( 'test_value' => '999_string', 'expected_value' => 999, ), - '99 string with spaces' => array( + '99 string with spaces' => array( 'test_value' => '99 string with spaces', 'expected_value' => 99, ), - '99 array' => array( + '99 array' => array( 'test_value' => array( 99 ), 'expected_value' => 1, ), - '99 string array' => array( + '99 string array' => array( 'test_value' => array( '99' ), 'expected_value' => 1, ), - 'PHP_INT_MIN int' => array( + 'PHP_INT_MAX int' => array( + 'test_value' => PHP_INT_MAX, + 'expected_value' => PHP_INT_MAX, + ), + 'PHP_INT_MIN int' => array( 'test_value' => PHP_INT_MIN, 'expected_value' => PHP_INT_MAX, ), - 'PHP_INT_MIN string' => array( + // The adjacent value is representable, so it does not need clamping. + 'PHP_INT_MIN + 1 int' => array( + 'test_value' => PHP_INT_MIN + 1, + 'expected_value' => PHP_INT_MAX, + ), + 'PHP_INT_MAX string' => array( + 'test_value' => '9223372036854775807', + 'expected_value' => PHP_INT_MAX, + ), + 'PHP_INT_MIN string' => array( 'test_value' => '-9223372036854775808', 'expected_value' => PHP_INT_MAX, ), - 'out of range negative' => array( + /* + * Casting a numeric string beyond the integer range clamps without + * overflowing, so this needs no special handling. PHP 8.5.0 and 8.5.1 + * warned here, which was reverted in PHP 8.5.2. + */ + 'PHP_INT_MAX * 1000 string' => array( + 'test_value' => '9223372036854775807000', + 'expected_value' => PHP_INT_MAX, + ), + 'out of range negative string' => array( 'test_value' => '-99999999999999999999', 'expected_value' => PHP_INT_MAX, ), + 'out of range positive string' => array( + 'test_value' => '99999999999999999999', + 'expected_value' => PHP_INT_MAX, + ), + 'out of range float string' => array( + 'test_value' => '1.0e30', + 'expected_value' => PHP_INT_MAX, + ), + 'out of range negative float' => array( + 'test_value' => -1.0e30, + 'expected_value' => PHP_INT_MAX, + ), + 'out of range positive float' => array( + 'test_value' => 1.0e30, + 'expected_value' => PHP_INT_MAX, + ), + 'PHP_INT_MAX as a float' => array( + 'test_value' => (float) PHP_INT_MAX, + 'expected_value' => PHP_INT_MAX, + ), + 'PHP_INT_MIN as a float' => array( + 'test_value' => (float) PHP_INT_MIN, + 'expected_value' => PHP_INT_MAX, + ), + /* + * Integer arithmetic silently overflows to a float, which is the most + * likely way an out of range value reaches this function. + */ + 'PHP_INT_MAX + 1' => array( + 'test_value' => PHP_INT_MAX + 1, + 'expected_value' => PHP_INT_MAX, + ), + 'PHP_INT_MIN - 1' => array( + 'test_value' => PHP_INT_MIN - 1, + 'expected_value' => PHP_INT_MAX, + ), + /* + * Overflowing to exactly 2**64 wraps to 0 when cast, rather than to an + * arbitrary value, so it is easily mistaken for a legitimate result. + */ + 'PHP_INT_MAX * 2' => array( + 'test_value' => PHP_INT_MAX * 2, + 'expected_value' => PHP_INT_MAX, + ), + 'PHP_INT_MIN * 2' => array( + 'test_value' => PHP_INT_MIN * 2, + 'expected_value' => PHP_INT_MAX, + ), + 'in range float' => array( + 'test_value' => 2.0 ** 63 - 2048.0, + 'expected_value' => 9223372036854773760, + ), + 'INF' => array( + 'test_value' => INF, + 'expected_value' => PHP_INT_MAX, + ), + '-INF' => array( + 'test_value' => -INF, + 'expected_value' => PHP_INT_MAX, + ), + 'NAN' => array( + 'test_value' => NAN, + 'expected_value' => 0, + ), ); } } From d58d3074683c29950828492224cd8693ffe7151d Mon Sep 17 00:00:00 2001 From: Joseph Scott Date: Fri, 7 Aug 2026 07:23:11 -0600 Subject: [PATCH 3/3] Update src/wp-includes/load.php Co-authored-by: Weston Ruter --- src/wp-includes/load.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/load.php b/src/wp-includes/load.php index d82d05fa7a98e..665b5c98cf1e8 100644 --- a/src/wp-includes/load.php +++ b/src/wp-includes/load.php @@ -1486,12 +1486,18 @@ function absint( $maybeint ): int { } } + $intval = (int) $maybeint; + /* * PHP_INT_MIN is the only integer whose absolute value is larger than * PHP_INT_MAX, which would make abs() return a float. Clamping it to * -PHP_INT_MAX first keeps the result within the integer range. */ - return abs( max( (int) $maybeint, -PHP_INT_MAX ) ); + if ( PHP_INT_MIN === $intval ) { + return PHP_INT_MAX; + } + + return abs( $intval ); } /**