From 0338a00e94d227e518e04e1365ce3a3a80a3c5f9 Mon Sep 17 00:00:00 2001 From: Louis-Arnaud Date: Wed, 2 Sep 2026 07:27:04 +0200 Subject: [PATCH 1/4] ext/pcntl: fix declared signature of pcntl_signal($restart_syscalls) (#23531) pcntl_signal() parses its third argument with Z_PARAM_BOOL_OR_NULL(), so null is accepted, and null is also what selects the SIGALRM specific default of false. The stub declared a non-nullable bool defaulting to true, so Reflection reported a signature the implementation does not honour. Co-authored-by: NickSdot <32384907+NickSdot@users.noreply.github.com> --- ext/pcntl/pcntl.stub.php | 2 +- ext/pcntl/pcntl_arginfo.h | 4 +-- ext/pcntl/pcntl_decl.h | 8 ++--- .../tests/pcntl_signal_restart_syscalls.phpt | 36 +++++++++++++++++++ 4 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 ext/pcntl/tests/pcntl_signal_restart_syscalls.phpt diff --git a/ext/pcntl/pcntl.stub.php b/ext/pcntl/pcntl.stub.php index 4a4b8fe86931..1ab52d025036 100644 --- a/ext/pcntl/pcntl.stub.php +++ b/ext/pcntl/pcntl.stub.php @@ -1023,7 +1023,7 @@ function pcntl_waitid(int $idtype = P_ALL, ?int $id = null, &$info = [], int $fl function pcntl_wait(&$status, int $flags = 0, &$resource_usage = []): int {} /** @param callable|int $handler */ - function pcntl_signal(int $signal, $handler, bool $restart_syscalls = true): bool {} + function pcntl_signal(int $signal, $handler, ?bool $restart_syscalls = null): bool {} /** @return callable|int */ function pcntl_signal_get_handler(int $signal) {} diff --git a/ext/pcntl/pcntl_arginfo.h b/ext/pcntl/pcntl_arginfo.h index 2da7c8ad5db8..a0ba6f712e5f 100644 --- a/ext/pcntl/pcntl_arginfo.h +++ b/ext/pcntl/pcntl_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit pcntl.stub.php instead. - * Stub hash: 04e7b30c6fb23cf6ce6bc26fe094fd5b4dbfe826 + * Stub hash: ec6306e93fad6d127ff880fc01736ac287619cf7 * Has decl header: yes */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_pcntl_fork, 0, 0, IS_LONG, 0) @@ -31,7 +31,7 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_pcntl_signal, 0, 2, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, signal, IS_LONG, 0) ZEND_ARG_INFO(0, handler) - ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, restart_syscalls, _IS_BOOL, 0, "true") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, restart_syscalls, _IS_BOOL, 1, "null") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_pcntl_signal_get_handler, 0, 0, 1) diff --git a/ext/pcntl/pcntl_decl.h b/ext/pcntl/pcntl_decl.h index 7f8e5172cedb..a0fdb4dde755 100644 --- a/ext/pcntl/pcntl_decl.h +++ b/ext/pcntl/pcntl_decl.h @@ -1,8 +1,8 @@ /* This is a generated file, edit pcntl.stub.php instead. - * Stub hash: 04e7b30c6fb23cf6ce6bc26fe094fd5b4dbfe826 */ + * Stub hash: ec6306e93fad6d127ff880fc01736ac287619cf7 */ -#ifndef ZEND_PCNTL_DECL_04e7b30c6fb23cf6ce6bc26fe094fd5b4dbfe826_H -#define ZEND_PCNTL_DECL_04e7b30c6fb23cf6ce6bc26fe094fd5b4dbfe826_H +#ifndef ZEND_PCNTL_DECL_ec6306e93fad6d127ff880fc01736ac287619cf7_H +#define ZEND_PCNTL_DECL_ec6306e93fad6d127ff880fc01736ac287619cf7_H typedef enum zend_enum_Pcntl_QosClass { ZEND_ENUM_Pcntl_QosClass_UserInteractive = 1, @@ -12,4 +12,4 @@ typedef enum zend_enum_Pcntl_QosClass { ZEND_ENUM_Pcntl_QosClass_Background = 5, } zend_enum_Pcntl_QosClass; -#endif /* ZEND_PCNTL_DECL_04e7b30c6fb23cf6ce6bc26fe094fd5b4dbfe826_H */ +#endif /* ZEND_PCNTL_DECL_ec6306e93fad6d127ff880fc01736ac287619cf7_H */ diff --git a/ext/pcntl/tests/pcntl_signal_restart_syscalls.phpt b/ext/pcntl/tests/pcntl_signal_restart_syscalls.phpt new file mode 100644 index 000000000000..693f1efebf9b --- /dev/null +++ b/ext/pcntl/tests/pcntl_signal_restart_syscalls.phpt @@ -0,0 +1,36 @@ +--TEST-- +pcntl_signal(): $restart_syscalls is nullable +--EXTENSIONS-- +pcntl +--FILE-- +getParameters()[2]; +var_dump((string) $parameter->getType()); +var_dump($parameter->allowsNull()); +var_dump($parameter->getDefaultValue()); + +var_dump(pcntl_signal(SIGALRM, SIG_IGN)); +var_dump(pcntl_signal(SIGALRM, SIG_IGN, null)); +var_dump(pcntl_signal(SIGALRM, SIG_IGN, true)); +var_dump(pcntl_signal(SIGALRM, SIG_IGN, false)); + +try { + pcntl_signal(SIGALRM, SIG_IGN, 1); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + +var_dump(pcntl_signal(SIGALRM, SIG_DFL)); +?> +--EXPECT-- +string(5) "?bool" +bool(true) +NULL +bool(true) +bool(true) +bool(true) +bool(true) +TypeError: pcntl_signal(): Argument #3 ($restart_syscalls) must be of type ?bool, int given +bool(true) From df2090a39330a9f482fdc1c9f33cde4af3aadf32 Mon Sep 17 00:00:00 2001 From: Louis-Arnaud Date: Wed, 2 Sep 2026 08:28:59 +0200 Subject: [PATCH 2/4] ext/hash: report argument #2 ($filename) for null bytes in hash_file() (#23534) php_hash_do_hash() hardcodes argument index 1 in the isfilename branch, so hash_file() blames $algo for a null byte carried by $filename. hash_file() is the only caller passing isfilename = 1, and its filename is argument #2; php_hash_do_hash_hmac() already uses 2 for the same check. Co-authored-by: NickSdot <32384907+NickSdot@users.noreply.github.com> --- ext/hash/hash.c | 2 +- ext/hash/tests/hash_file_error.phpt | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/ext/hash/hash.c b/ext/hash/hash.c index d57ae149691b..4a0b0c49437e 100644 --- a/ext/hash/hash.c +++ b/ext/hash/hash.c @@ -361,7 +361,7 @@ static void php_hash_do_hash( } if (isfilename) { if (zend_char_has_nul_byte(data, data_len)) { - zend_argument_value_error(1, "must not contain any null bytes"); + zend_argument_value_error(2, "must not contain any null bytes"); RETURN_THROWS(); } stream = php_stream_open_wrapper_ex(data, "rb", REPORT_ERRORS, NULL, FG(default_context)); diff --git a/ext/hash/tests/hash_file_error.phpt b/ext/hash/tests/hash_file_error.phpt index a381d3bdd63c..4ef84a62ddf4 100644 --- a/ext/hash/tests/hash_file_error.phpt +++ b/ext/hash/tests/hash_file_error.phpt @@ -19,6 +19,13 @@ try { echo $exception::class, ': ', $exception->getMessage(), "\n"; } +echo "\n-- Testing hash_file() function with a null byte in the filename --\n"; +try { + hash_file('md5', $filename . chr(0) . $filename); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + echo "\n-- Testing hash_file() function with a non-existent file --\n"; var_dump(hash_file('md5', 'nonexistent.txt')); @@ -36,6 +43,9 @@ unlink( $filename ); -- Testing hash_file() function with an unknown algorithm -- ValueError: hash_file(): Argument #1 ($algo) must be a valid hashing algorithm +-- Testing hash_file() function with a null byte in the filename -- +ValueError: hash_file(): Argument #2 ($filename) must not contain any null bytes + -- Testing hash_file() function with a non-existent file -- Warning: hash_file(): Failed to open stream: No such file or directory in %s on line %d From 2bbe62eb5677e4612eac880a27a3c5a9cba73128 Mon Sep 17 00:00:00 2001 From: Louis-Arnaud Date: Wed, 2 Sep 2026 11:59:45 +0200 Subject: [PATCH 3/4] ext/intl: report argument #3 ($variant) for an invalid IDNA variant (#23533) - php_intl_idn_handoff() takes domain, flags, variant, so the variant check must report argument 3; it hardcodes 2, which makes idn_to_ascii() and idn_to_utf8() blame $flags for a value carried by $variant. - The message concatenated PRId32 as a literal instead of using it as a conversion, so it read "must be less than d bytes" and INT32_MAX was never printed. --- ext/intl/idn/idn.cpp | 4 ++-- ext/intl/tests/idn_uts46_errors.phpt | 11 ++++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/ext/intl/idn/idn.cpp b/ext/intl/idn/idn.cpp index 76cca2281296..c1db9a84ccd0 100644 --- a/ext/intl/idn/idn.cpp +++ b/ext/intl/idn/idn.cpp @@ -122,11 +122,11 @@ static void php_intl_idn_handoff(INTERNAL_FUNCTION_PARAMETERS, int mode) RETURN_THROWS(); } if (UNEXPECTED(ZSTR_LEN(domain) > INT32_MAX - 1)) { - zend_argument_value_error(1, "must be less than " PRId32 " bytes", INT32_MAX); + zend_argument_value_error(1, "must be less than %" PRId32 " bytes", INT32_MAX); RETURN_THROWS(); } if (variant != INTL_IDN_VARIANT_UTS46) { - zend_argument_value_error(2, "must be INTL_IDNA_VARIANT_UTS46"); + zend_argument_value_error(3, "must be INTL_IDNA_VARIANT_UTS46"); RETURN_THROWS(); } /* don't check options; it wasn't checked before */ diff --git a/ext/intl/tests/idn_uts46_errors.phpt b/ext/intl/tests/idn_uts46_errors.phpt index c1058c3a2439..be7121482b7f 100644 --- a/ext/intl/tests/idn_uts46_errors.phpt +++ b/ext/intl/tests/idn_uts46_errors.phpt @@ -18,6 +18,13 @@ try { echo $e::class, ': ', $e->getMessage(), PHP_EOL; } +echo "bad variant, named argument:", "\n"; +try { + var_dump(idn_to_utf8("xn--fuball-cta.com", variant: INTL_IDNA_VARIANT_UTS46 + 10)); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + echo "empty domain:", "\n"; try { var_dump(idn_to_ascii("", 0, INTL_IDNA_VARIANT_UTS46)); @@ -45,7 +52,9 @@ var_dump($foo["errors"]==IDNA_ERROR_CONTEXTJ); --EXPECT-- => PHP level errors bad variant: -ValueError: idn_to_ascii(): Argument #2 ($flags) must be INTL_IDNA_VARIANT_UTS46 +ValueError: idn_to_ascii(): Argument #3 ($variant) must be INTL_IDNA_VARIANT_UTS46 +bad variant, named argument: +ValueError: idn_to_utf8(): Argument #3 ($variant) must be INTL_IDNA_VARIANT_UTS46 empty domain: ValueError: idn_to_ascii(): Argument #1 ($domain) must not be empty with error, but no details arg: From 78c33e071e2fb92f01695c520e0828a32a3561ae Mon Sep 17 00:00:00 2001 From: David Carlier Date: Wed, 2 Sep 2026 11:06:28 +0100 Subject: [PATCH 4/4] [skip ci] Add NEWS entries --- NEWS | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/NEWS b/NEWS index 17ec66961b3b..2fd0fa3a3ace 100644 --- a/NEWS +++ b/NEWS @@ -12,6 +12,10 @@ PHP NEWS . Fixed stale getElementsByClassName() and other node list caches after className/classList writes and attribute removals. (Ilia Alshanetsky) +- Hash: + . Fixed hash_file() reporting argument #1 ($algo) instead of argument #2 + ($filename) when the filename contains null bytes. (lacatoire) + - Intl: . Fixed grapheme_strpos() and grapheme_strrpos() with an empty needle returning UTF-16 offsets instead of grapheme offsets. (Ilia Alshanetsky) @@ -26,12 +30,20 @@ PHP NEWS from compiled rules. (iliaal) . Fixed Spoofchecker methods not recording the ICU error code when an ICU call fails. (Ilia Alshanetsky) + . Fixed idn_to_ascii() and idn_to_utf8() reporting argument #2 ($flags) + instead of argument #3 ($variant) for an invalid IDNA variant, and the + domain length error message printing a literal "d" instead of the limit. + (lacatoire) - MBString: . Fixed mb_ereg_replace() emitting a NUL or out-of-bounds bytes in the replacement when a \k backref has no closing delimiter. (Ilia Alshanetsky) +- PCNTL: + . Fixed the declared signature of pcntl_signal(), whose $restart_syscalls + argument accepts null and defaults to it. (lacatoire) + - PDO_PGSQL: . Added Pdo\Pgsql::ATTR_CHUNK_SIZE to fetch a result set in chunks of the given number of rows. (KentarouTakeda)