[php-src] master: ext/standard: Fix `sleep` and `usleep` unsigned integer overflow (#22619)

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-07-07T02:28:19+08:00

Commit: https://github.com/php/php-src/commit/6beb0895a1f715c4fa0bed9eeb10c2a71d38db57
Raw diff: https://github.com/php/php-src/commit/6beb0895a1f715c4fa0bed9eeb10c2a71d38db57.diff

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

Changed paths:
  A  ext/standard/tests/general_functions/sleep_usleep_uint_overflow.phpt
  M  NEWS
  M  ext/standard/basic_functions.c
  M  ext/standard/tests/general_functions/sleep_error.phpt
  M  ext/standard/tests/general_functions/usleep_error.phpt


Diff:

diff --git a/NEWS b/NEWS
index 77ea35389d5f..11b470654384 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 f8b1cb5c4fdb..e81050323754 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 bfe66480fa03..47685fc3a5ae 100644
--- a/ext/standard/tests/general_functions/sleep_error.phpt
+++ b/ext/standard/tests/general_functions/sleep_error.phpt
@@ -7,7 +7,7 @@ sleep(-10);
 
 ?>
 --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 000000000000..6f0a7b3fa9e8
--- /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 46bd4420580b..9c3825f18b56 100644
--- a/ext/standard/tests/general_functions/usleep_error.phpt
+++ b/ext/standard/tests/general_functions/usleep_error.phpt
@@ -7,7 +7,7 @@ usleep(-10);
 
 ?>
 --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}
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.