Commit 6beb0895a1f for php.net
commit 6beb0895a1f715c4fa0bed9eeb10c2a71d38db57
Author: Weilin Du <weilindu@php.net>
Date: Tue Jul 7 02:28:19 2026 +0800
ext/standard: Fix `sleep` and `usleep` unsigned integer overflow (#22619)
sleep(4294967296) actually sleep 0 seconds because of a unsigned int overflow
bug. This is also true in usleep.
I target this to master as a fix that is classified as a stricter parsing. Now we
throw ValueError instead.
p.s. in Windows, the sleep function is implemented with
SleepEx (t * 1000, TRUE) not php_sleep, phew, so we need to lower the
threshold to UINT_MAX / 1000 in Windows
diff --git a/NEWS b/NEWS
index 77ea35389d5..11b47065438 100644
--- a/NEWS
+++ b/NEWS
@@ -51,6 +51,10 @@ PHP NEWS
. Fixed bug GH-22585 (OOM on bailout with uninitialized
do_request() parameters). (David Carlier)
+- Standard:
+ . Fixed sleep() and usleep() to reject values that overflow the underlying
+ unsigned int timeout. (Weilin Du)
+
- Streams:
. Fixed bug GH-21468 (Segfault in file_get_contents w/ a https URL
and a proxy set). (CVE-2026-12184) (ndossche)
diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c
index f8b1cb5c4fd..e8105032375 100644
--- a/ext/standard/basic_functions.c
+++ b/ext/standard/basic_functions.c
@@ -1123,13 +1123,18 @@ PHP_FUNCTION(flush)
PHP_FUNCTION(sleep)
{
zend_long num;
+#ifdef PHP_WIN32
+ const unsigned int max = UINT_MAX / 1000;
+#else
+ const unsigned int max = UINT_MAX;
+#endif
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_LONG(num)
ZEND_PARSE_PARAMETERS_END();
- if (num < 0) {
- zend_argument_value_error(1, "must be greater than or equal to 0");
+ if (num < 0 || (zend_ulong) num > max) {
+ zend_argument_value_error(1, "must be between 0 and %u", max);
RETURN_THROWS();
}
@@ -1146,8 +1151,8 @@ PHP_FUNCTION(usleep)
Z_PARAM_LONG(num)
ZEND_PARSE_PARAMETERS_END();
- if (num < 0) {
- zend_argument_value_error(1, "must be greater than or equal to 0");
+ if (num < 0 || (zend_ulong) num > UINT_MAX) {
+ zend_argument_value_error(1, "must be between 0 and %u", UINT_MAX);
RETURN_THROWS();
}
diff --git a/ext/standard/tests/general_functions/sleep_error.phpt b/ext/standard/tests/general_functions/sleep_error.phpt
index bfe66480fa0..47685fc3a5a 100644
--- a/ext/standard/tests/general_functions/sleep_error.phpt
+++ b/ext/standard/tests/general_functions/sleep_error.phpt
@@ -7,7 +7,7 @@
?>
--EXPECTF--
-Fatal error: Uncaught ValueError: sleep(): Argument #1 ($seconds) must be greater than or equal to 0 in %s:%d
+Fatal error: Uncaught ValueError: sleep(): Argument #1 ($seconds) must be between 0 and %d in %s:%d
Stack trace:
#0 %s(%d): sleep(-10)
#1 {main}
diff --git a/ext/standard/tests/general_functions/sleep_usleep_uint_overflow.phpt b/ext/standard/tests/general_functions/sleep_usleep_uint_overflow.phpt
new file mode 100644
index 00000000000..6f0a7b3fa9e
--- /dev/null
+++ b/ext/standard/tests/general_functions/sleep_usleep_uint_overflow.phpt
@@ -0,0 +1,25 @@
+--TEST--
+sleep() and usleep() reject values above their unsigned int limits
+--SKIPIF--
+<?php
+if (PHP_INT_SIZE < 8) die('skip 64-bit only');
+?>
+--FILE--
+<?php
+var_dump(sleep(0));
+usleep(0);
+echo "usleep(0) ok\n";
+
+foreach (['sleep', 'usleep'] as $function) {
+ try {
+ $function(4294967296);
+ } catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+ }
+}
+?>
+--EXPECTF--
+int(0)
+usleep(0) ok
+sleep(): Argument #1 ($seconds) must be between 0 and %d
+usleep(): Argument #1 ($microseconds) must be between 0 and 4294967295
diff --git a/ext/standard/tests/general_functions/usleep_error.phpt b/ext/standard/tests/general_functions/usleep_error.phpt
index 46bd4420580..9c3825f18b5 100644
--- a/ext/standard/tests/general_functions/usleep_error.phpt
+++ b/ext/standard/tests/general_functions/usleep_error.phpt
@@ -7,7 +7,7 @@
?>
--EXPECTF--
-Fatal error: Uncaught ValueError: usleep(): Argument #1 ($microseconds) must be greater than or equal to 0 in %s:%d
+Fatal error: Uncaught ValueError: usleep(): Argument #1 ($microseconds) must be between 0 and 4294967295 in %s:%d
Stack trace:
#0 %s(%d): usleep(-10)
#1 {main}