diff --git a/NEWS b/NEWS index fd5b2d859ee0..ade0b853f4da 100644 --- a/NEWS +++ b/NEWS @@ -49,6 +49,10 @@ PHP NEWS TCP_USER_TIMEOUT, and SO_LINGER options. (Weilin Du) . Fixed various memory related issues in ext/sockets. (David Carlier) +- Standard: + . Fixed setlocale() to reject locale names containing NUL bytes instead of + silently truncating them. (Weilin Du) + - Streams: . Added a new IO copy API used by php_stream_copy_to_stream_ex() that leverages platform primitives (sendfile, splice, copy_file_range, diff --git a/UPGRADING b/UPGRADING index 077a7faf6562..40b0bbb0a2d2 100644 --- a/UPGRADING +++ b/UPGRADING @@ -201,6 +201,8 @@ PHP 8.6 UPGRADE NOTES bytes. . parse_str() now raises a ValueError when the $string argument contains NUL bytes. + . setlocale() now raises a ValueError when a locale name contains NUL bytes, + instead of silently truncating it. . linkinfo() now raises a ValueError when the $path argument is empty. . pathinfo() now raises a ValueError when an invalid $flag argument value is passed. diff --git a/ext/standard/string.c b/ext/standard/string.c index 823c32a8cf1e..56bf6df87160 100644 --- a/ext/standard/string.c +++ b/ext/standard/string.c @@ -4909,6 +4909,11 @@ static zend_string *try_setlocale_zval(zend_long cat, zval *loc_zv) { if (UNEXPECTED(loc_str == NULL)) { return NULL; } + if (zend_str_has_nul_byte(loc_str)) { + zend_argument_value_error(2, "must not contain any null bytes"); + zend_tmp_string_release(tmp_loc_str); + return NULL; + } zend_string *result = try_setlocale_str(cat, loc_str); zend_tmp_string_release(tmp_loc_str); return result; @@ -4930,8 +4935,11 @@ PHP_FUNCTION(setlocale) zend_string **strings = do_alloca(sizeof(zend_string *) * num_args, use_heap); for (uint32_t i = 0; i < num_args; i++) { - if (UNEXPECTED(Z_TYPE(args[i]) != IS_ARRAY && !zend_parse_arg_str(&args[i], &strings[i], true, i + 2))) { - zend_wrong_parameter_type_error(i + 2, Z_EXPECTED_ARRAY_OR_STRING_OR_NULL, &args[i]); + if (UNEXPECTED(Z_TYPE(args[i]) != IS_ARRAY && !zend_parse_arg_path_str(&args[i], &strings[i], true, i + 2))) { + zend_wrong_parameter_type_error( + i + 2, + Z_TYPE(args[i]) == IS_STRING ? Z_EXPECTED_PATH : Z_EXPECTED_ARRAY_OR_STRING_OR_NULL, + &args[i]); goto out; } } diff --git a/ext/standard/tests/strings/setlocale_null_byte.phpt b/ext/standard/tests/strings/setlocale_null_byte.phpt new file mode 100644 index 000000000000..e84b8f8d203f --- /dev/null +++ b/ext/standard/tests/strings/setlocale_null_byte.phpt @@ -0,0 +1,39 @@ +--TEST-- +setlocale() rejects locale names with null bytes +--FILE-- +getMessage(), "\n"; +} + +try { + var_dump(setlocale(LC_ALL, ["locale\0name", "C"])); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + +try { + var_dump(@setlocale(LC_ALL, [str_repeat("x", 255), "C\0locale"])); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + +try { + var_dump(setlocale(LC_ALL, [], new NullByteStringable())); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECT-- +setlocale(): Argument #2 ($locales) must not contain any null bytes +setlocale(): Argument #2 ($locales) must not contain any null bytes +setlocale(): Argument #2 ($locales) must not contain any null bytes +setlocale(): Argument #3 must not contain any null bytes