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}