[php-src] master: ext/standard: Io\Poll\Context::wait() max events range check

Marc Bennewitz via David Carlier <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Marc Bennewitz (marc-mabe)
Committer: David Carlier (devnexen)
Date: 2026-08-27T21:38:23+01:00

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

ext/standard: Io\Poll\Context::wait() max events range check

Reject a $maxEvents value greater than INT_MAX instead of truncating it,
which prevents an integer overflow where SIZEOF_INT < SIZEOF_ZEND_LONG.

Close GH-23468

Changed paths:
  A  ext/standard/tests/poll/poll_ctx_wait_error.phpt
  A  ext/standard/tests/poll/poll_ctx_wait_error_int64.phpt
  D  ext/standard/tests/poll/poll_ctx_wait.phpt
  M  NEWS
  M  ext/standard/io_poll.c


Diff:

diff --git a/NEWS b/NEWS
index 871c1c9adde7..df78bf6abe8a 100644
--- a/NEWS
+++ b/NEWS
@@ -30,6 +30,8 @@ PHP                                                                        NEWS
   . Fixed an out-of-bounds read when following a redirect response with an
     empty Location header. (iliaal)
   . Fixed read buffer compaction in php_stream_filter_flush(). (crystarm)
+  . Io\Poll\Context::wait() now rejects a $maxEvents value greater than
+    INT_MAX instead of truncating it. (marc-mabe)
 
 
 27 Aug 2026, PHP 8.6.0beta2
diff --git a/ext/standard/io_poll.c b/ext/standard/io_poll.c
index f57813874d52..a8a0563627fa 100644
--- a/ext/standard/io_poll.c
+++ b/ext/standard/io_poll.c
@@ -804,12 +804,15 @@ PHP_METHOD(Io_Poll_Context, wait)
 		if (max_events <= 0) {
 			max_events = 64;
 		}
-	} else if (max_events <= 0) {
+	} else if (UNEXPECTED(max_events <= 0)) {
 		zend_argument_value_error(2, "must be greater than 0");
 		RETURN_THROWS();
+	} else if (ZEND_LONG_INT_OVFL(max_events)) {
+		zend_argument_value_error(2, "must be less than or equal to %d", INT_MAX);
+		RETURN_THROWS();
 	}
 
-	php_poll_event *events = safe_emalloc(max_events, sizeof(*events), 0);
+	php_poll_event *events = safe_emalloc((size_t) max_events, sizeof(*events), 0);
 	int num_events = php_poll_wait(intern->ctx, events, (int) max_events, timeout ? &timeout_ts : NULL);
 
 	if (num_events < 0) {
diff --git a/ext/standard/tests/poll/poll_ctx_wait.phpt b/ext/standard/tests/poll/poll_ctx_wait_error.phpt
similarity index 58%
rename from ext/standard/tests/poll/poll_ctx_wait.phpt
rename to ext/standard/tests/poll/poll_ctx_wait_error.phpt
index 5080c1421fdb..c514884c3c6f 100644
--- a/ext/standard/tests/poll/poll_ctx_wait.phpt
+++ b/ext/standard/tests/poll/poll_ctx_wait_error.phpt
@@ -12,13 +12,27 @@ try {
     echo $e::class, ': ', $e->getMessage(), PHP_EOL;
 }
 
+try {
+    $poll_ctx->wait(maxEvents: PHP_INT_MIN);
+} catch (Throwable $e) {
+    echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+
 try {
     $poll_ctx->wait(maxEvents: -1);
 } catch (Throwable $e) {
     echo $e::class, ': ', $e->getMessage(), PHP_EOL;
 }
 
+try {
+    $poll_ctx->wait(maxEvents: 0);
+} catch (Throwable $e) {
+    echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+
 ?>
 --EXPECT--
 ValueError: Io\Poll\Context::wait(): Argument #1 ($timeout) must not be negative
 ValueError: Io\Poll\Context::wait(): Argument #2 ($maxEvents) must be greater than 0
+ValueError: Io\Poll\Context::wait(): Argument #2 ($maxEvents) must be greater than 0
+ValueError: Io\Poll\Context::wait(): Argument #2 ($maxEvents) must be greater than 0
diff --git a/ext/standard/tests/poll/poll_ctx_wait_error_int64.phpt b/ext/standard/tests/poll/poll_ctx_wait_error_int64.phpt
new file mode 100644
index 000000000000..3d881050e655
--- /dev/null
+++ b/ext/standard/tests/poll/poll_ctx_wait_error_int64.phpt
@@ -0,0 +1,23 @@
+--TEST--
+Io\Poll\Context::wait(): Parameter validation upper limit
+--SKIPIF--
+<?php
+if (PHP_INT_SIZE <= 4) {
+    die("skip this test is for > 32bit platforms only");
+}
+?>
+--FILE--
+<?php
+require_once __DIR__ . '/poll.inc';
+
+$poll_ctx = new Io\Poll\Context();
+
+try {
+    $poll_ctx->wait(maxEvents: PHP_INT_MAX);
+} catch (Throwable $e) {
+    echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+ValueError: Io\Poll\Context::wait(): Argument #2 ($maxEvents) must be less than or equal to 2147483647
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.