From 6420da42c95370f9b3af68b250c1b1ce57c0bf8c Mon Sep 17 00:00:00 2001 From: lacatoire Date: Tue, 18 Aug 2026 11:03:47 +0200 Subject: [PATCH] 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. --- ext/pcntl/pcntl.c | 7 +-- ext/pcntl/tests/pcntl_cpuaffinity_bound.phpt | 46 ++++++++++++++++++++ ext/pcntl/tests/pcntl_qosclass.phpt | 5 +++ 3 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 ext/pcntl/tests/pcntl_cpuaffinity_bound.phpt diff --git a/ext/pcntl/pcntl.c b/ext/pcntl/pcntl.c index 265b47e52dc7..0a7c31dab603 100644 --- a/ext/pcntl/pcntl.c +++ b/ext/pcntl/pcntl.c @@ -1773,7 +1773,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(); } @@ -1880,9 +1880,10 @@ PHP_FUNCTION(pcntl_getqos_class) PHP_FUNCTION(pcntl_setqos_class) { - zend_enum_Pcntl_QosClass qos; + zend_enum_Pcntl_QosClass qos = ZEND_ENUM_Pcntl_QosClass_Default; - ZEND_PARSE_PARAMETERS_START(1, 1) + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL Z_PARAM_ENUM(qos, QosClass_ce) ZEND_PARSE_PARAMETERS_END(); diff --git a/ext/pcntl/tests/pcntl_cpuaffinity_bound.phpt b/ext/pcntl/tests/pcntl_cpuaffinity_bound.phpt new file mode 100644 index 000000000000..e1b937aa0c0b --- /dev/null +++ b/ext/pcntl/tests/pcntl_cpuaffinity_bound.phpt @@ -0,0 +1,46 @@ +--TEST-- +pcntl_setcpuaffinity(): the upper bound the error advertises is itself a valid cpu id +--EXTENSIONS-- +pcntl +--SKIPIF-- + +--FILE-- +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 (ValueError $e) { + 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 (ValueError $e) { + var_dump($e->getMessage() === $prefix . $bound . ' (' . ($bound + 1) . ')'); +} +?> +--EXPECT-- +bool(true) +bool(true) diff --git a/ext/pcntl/tests/pcntl_qosclass.phpt b/ext/pcntl/tests/pcntl_qosclass.phpt index f8ca1a706bd2..947afdb4836b 100644 --- a/ext/pcntl/tests/pcntl_qosclass.phpt +++ b/ext/pcntl/tests/pcntl_qosclass.phpt @@ -13,7 +13,12 @@ pcntl_setqos_class(Pcntl\QosClass::Default); 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)