Commit e23dc68f119 for php
commit e23dc68f1199bf69524fb84e3e1b0e295c0fb863
Author: lacatoire <la.catoire@gmail.com>
Date: Sat Sep 26 18:22:54 2026 +0100
ext/pcntl: fix an off by one bound and a parameter the parser made required
pcntl_setcpuaffinity() refuses a cpu id equal to the cpu count but named
that count as the inclusive upper bound, printing a zend_long with the
unsigned format on the way. pcntl_setqos_class() declares $qos_class
optional while its parser demanded it, leaving the declared default
unreachable.
Close GH-23553
diff --git a/NEWS b/NEWS
index 8d34aad3fe6..fb25e3a0b80 100644
--- a/NEWS
+++ b/NEWS
@@ -70,6 +70,10 @@ PHP NEWS
an exception is pending. (nicolas-grekas)
. Fixed pcntl_signal_dispatch() dropping the signals queued behind a handler
that throws. (nicolas-grekas)
+ . Fixed pcntl_setcpuaffinity() error message advertising the cpu count as
+ the inclusive upper bound. (lacatoire)
+ . Fixed pcntl_setqos_class() requiring its optional $qos_class argument.
+ (lacatoire)
- PDO:
. Fixed PDOStatement::getColumnMeta() reading out of bounds for an invalid
diff --git a/ext/pcntl/pcntl.c b/ext/pcntl/pcntl.c
index b55e1417d11..34a67028263 100644
--- a/ext/pcntl/pcntl.c
+++ b/ext/pcntl/pcntl.c
@@ -1791,7 +1791,7 @@ PHP_FUNCTION(pcntl_setcpuaffinity)
}
if (cpu < 0 || cpu >= maxcpus) {
- zend_argument_value_error(2, "cpu id must be between 0 and " ZEND_ULONG_FMT " (" ZEND_LONG_FMT ")", maxcpus, cpu);
+ zend_argument_value_error(2, "cpu id must be between 0 and " ZEND_LONG_FMT " (" ZEND_LONG_FMT ")", maxcpus - 1, cpu);
PCNTL_CPU_DESTROY(mask);
RETURN_THROWS();
}
@@ -1898,13 +1898,14 @@ PHP_FUNCTION(pcntl_getqos_class)
PHP_FUNCTION(pcntl_setqos_class)
{
- zval *qos_obj;
+ zval *qos_obj = NULL;
- ZEND_PARSE_PARAMETERS_START(1, 1)
+ ZEND_PARSE_PARAMETERS_START(0, 1)
+ Z_PARAM_OPTIONAL
Z_PARAM_OBJECT_OF_CLASS(qos_obj, QosClass_ce)
ZEND_PARSE_PARAMETERS_END();
- qos_class_t qos_class = qos_zval_to_lval(qos_obj);
+ qos_class_t qos_class = qos_obj ? qos_zval_to_lval(qos_obj) : QOS_CLASS_DEFAULT;
if (UNEXPECTED(pthread_set_qos_class_self_np((qos_class_t)qos_class, 0) != 0))
{
diff --git a/ext/pcntl/tests/pcntl_cpuaffinity_bound.phpt b/ext/pcntl/tests/pcntl_cpuaffinity_bound.phpt
new file mode 100644
index 00000000000..6b415a52c24
--- /dev/null
+++ b/ext/pcntl/tests/pcntl_cpuaffinity_bound.phpt
@@ -0,0 +1,52 @@
+--TEST--
+pcntl_setcpuaffinity(): the upper bound the error advertises is itself a valid cpu id
+--EXTENSIONS--
+pcntl
+--SKIPIF--
+<?php
+if (PHP_OS_FAMILY === 'Solaris') {
+ die("skip broken pset_create()");
+}
+if (!function_exists("pcntl_setcpuaffinity")) die("skip pcntl_setcpuaffinity is not available");
+?>
+--FILE--
+<?php
+$pid = getmypid();
+$prefix = 'pcntl_setcpuaffinity(): Argument #2 ($cpu_ids) cpu id must be between 0 and ';
+
+/* read the advertised bound out of the message itself */
+try {
+ pcntl_setcpuaffinity($pid, [PHP_INT_MAX]);
+ exit("PHP_INT_MAX was accepted as a cpu id" . PHP_EOL);
+} catch (Throwable $e) {
+ echo $e::class, "\n";
+ if (!preg_match('/must be between 0 and (\d+) \(/', $e->getMessage(), $m)) {
+ exit("unexpected message: " . $e->getMessage() . PHP_EOL);
+ }
+}
+$bound = (int) $m[1];
+
+/* Every id is range checked before any syscall runs, so pairing the advertised
+ bound with an out of range id shows which of the two the check rejects,
+ without ever changing the process affinity. */
+try {
+ pcntl_setcpuaffinity($pid, [$bound, PHP_INT_MAX]);
+} catch (Throwable $e) {
+ echo $e::class, "\n";
+ var_dump($e->getMessage() === $prefix . $bound . ' (' . PHP_INT_MAX . ')');
+}
+
+/* and the first id past the bound is rejected, naming itself */
+try {
+ pcntl_setcpuaffinity($pid, [$bound + 1]);
+} catch (Throwable $e) {
+ echo $e::class, "\n";
+ var_dump($e->getMessage() === $prefix . $bound . ' (' . ($bound + 1) . ')');
+}
+?>
+--EXPECT--
+ValueError
+ValueError
+bool(true)
+ValueError
+bool(true)
diff --git a/ext/pcntl/tests/pcntl_qosclass.phpt b/ext/pcntl/tests/pcntl_qosclass.phpt
index f8ca1a706bd..947afdb4836 100644
--- a/ext/pcntl/tests/pcntl_qosclass.phpt
+++ b/ext/pcntl/tests/pcntl_qosclass.phpt
@@ -13,7 +13,12 @@
var_dump(Pcntl\QosClass::Default === pcntl_getqos_class());
pcntl_setqos_class(Pcntl\QosClass::Background);
var_dump(Pcntl\QosClass::Background == pcntl_getqos_class());
+
+/* the parameter is optional, and omitting it applies the declared default */
+pcntl_setqos_class();
+var_dump(Pcntl\QosClass::Default === pcntl_getqos_class());
?>
--EXPECT--
bool(true)
bool(true)
+bool(true)