Merge branch 'PHP-8.1' into PHP-8.2

* PHP-8.1:
  Fix GH-8065: opcache.consistency_checks > 0 causes segfaults in PHP >= 8.1.5 in fpm context
  Fix GH-8646: Memory leak PHP FPM 8.1
This commit is contained in:
Niels Dossche 2023-03-07 20:26:57 +01:00
commit 7682868dd1
8 changed files with 138 additions and 4 deletions

3
NEWS
View file

@ -6,12 +6,15 @@ PHP NEWS
. Added optional support for max_execution_time in ZTS/Linux builds
(Kévin Dunglas)
. Fixed use-after-free in recursive AST evaluation. (ilutov)
. Fixed bug GH-8646 (Memory leak PHP FPM 8.1). (nielsdos)
- FTP:
. Propagate success status of ftp_close(). (nielsdos)
- Opcache:
. Fixed build for macOS to cater with pkg-config settings. (David Carlier)
. Fixed bug GH-8065 (opcache.consistency_checks > 0 causes segfaults in
PHP >= 8.1.5 in fpm context). (nielsdos)
- OpenSSL:
. Add missing error checks on file writing functions. (nielsdos)

View file

@ -1267,6 +1267,34 @@ ZEND_API void zend_deactivate(void) /* {{{ */
zend_destroy_rsrc_list(&EG(regular_list));
/* See GH-8646: https://github.com/php/php-src/issues/8646
*
* Interned strings that hold class entries can get a corresponding slot in map_ptr for the CE cache.
* map_ptr works like a bump allocator: there is a counter which increases to allocate the next slot in the map.
*
* For class name strings in non-opcache we have:
* - on startup: permanent + interned
* - on request: interned
* For class name strings in opcache we have:
* - on startup: permanent + interned
* - on request: either not interned at all, which we can ignore because they won't get a CE cache entry
* or they were already permanent + interned
* or we get a new permanent + interned string in the opcache persistence code
*
* Notice that the map_ptr layout always has the permanent strings first, and the request strings after.
* In non-opcache, a request string may get a slot in map_ptr, and that interned request string
* gets destroyed at the end of the request. The corresponding map_ptr slot can thereafter never be used again.
* This causes map_ptr to keep reallocating to larger and larger sizes.
*
* We solve it as follows:
* We can check whether we had any interned request strings, which only happens in non-opcache.
* If we have any, we reset map_ptr to the last permanent string.
* We can't lose any permanent strings because of map_ptr's layout.
*/
if (zend_hash_num_elements(&CG(interned_strings)) > 0) {
zend_map_ptr_reset();
}
#if GC_BENCH
fprintf(stderr, "GC Statistics\n");
fprintf(stderr, "-------------\n");

View file

@ -0,0 +1,26 @@
--TEST--
GH-8065: opcache.consistency_checks > 0 causes segfaults in PHP >= 8.1.5 in fpm context
--EXTENSIONS--
opcache
--INI--
opcache.enable_cli=1
opcache.consistency_checks=1
opcache.log_verbosity_level=2
--FILE--
<?php
var_dump(ini_get("opcache.consistency_checks"));
var_dump(ini_set("opcache.consistency_checks", 1));
var_dump(ini_set("opcache.consistency_checks", -1));
var_dump(ini_set("opcache.consistency_checks", 0));
?>
--EXPECTF--
%sWarning opcache.consistency_checks is reset back to 0 because it does not work properly (see GH-8065, GH-10624).
string(1) "0"
%sWarning opcache.consistency_checks is reset back to 0 because it does not work properly (see GH-8065, GH-10624).
bool(false)
%sWarning opcache.consistency_checks is reset back to 0 because it does not work properly (see GH-8065, GH-10624).
bool(false)
string(1) "0"

View file

@ -128,6 +128,19 @@ static ZEND_INI_MH(OnUpdateMaxWastedPercentage)
return SUCCESS;
}
static ZEND_INI_MH(OnUpdateConsistencyChecks)
{
zend_long *p = (zend_long *) ZEND_INI_GET_ADDR();
zend_long consistency_checks = atoi(ZSTR_VAL(new_value));
if (consistency_checks != 0) {
zend_accel_error(ACCEL_LOG_WARNING, "opcache.consistency_checks is reset back to 0 because it does not work properly (see GH-8065, GH-10624).\n");
return FAILURE;
}
*p = 0;
return SUCCESS;
}
static ZEND_INI_MH(OnEnable)
{
if (stage == ZEND_INI_STAGE_STARTUP ||
@ -262,7 +275,7 @@ ZEND_INI_BEGIN()
STD_PHP_INI_ENTRY("opcache.interned_strings_buffer", "8" , PHP_INI_SYSTEM, OnUpdateInternedStringsBuffer, accel_directives.interned_strings_buffer, zend_accel_globals, accel_globals)
STD_PHP_INI_ENTRY("opcache.max_accelerated_files" , "10000", PHP_INI_SYSTEM, OnUpdateMaxAcceleratedFiles, accel_directives.max_accelerated_files, zend_accel_globals, accel_globals)
STD_PHP_INI_ENTRY("opcache.max_wasted_percentage" , "5" , PHP_INI_SYSTEM, OnUpdateMaxWastedPercentage, accel_directives.max_wasted_percentage, zend_accel_globals, accel_globals)
STD_PHP_INI_ENTRY("opcache.consistency_checks" , "0" , PHP_INI_ALL , OnUpdateLong, accel_directives.consistency_checks, zend_accel_globals, accel_globals)
STD_PHP_INI_ENTRY("opcache.consistency_checks" , "0" , PHP_INI_ALL , OnUpdateConsistencyChecks, accel_directives.consistency_checks, zend_accel_globals, accel_globals)
STD_PHP_INI_ENTRY("opcache.force_restart_timeout" , "180" , PHP_INI_SYSTEM, OnUpdateLong, accel_directives.force_restart_timeout, zend_accel_globals, accel_globals)
STD_PHP_INI_ENTRY("opcache.revalidate_freq" , "2" , PHP_INI_ALL , OnUpdateLong, accel_directives.revalidate_freq, zend_accel_globals, accel_globals)
STD_PHP_INI_ENTRY("opcache.file_update_protection", "2" , PHP_INI_ALL , OnUpdateLong, accel_directives.file_update_protection, zend_accel_globals, accel_globals)

View file

@ -457,6 +457,12 @@ static ZEND_FUNCTION(zend_test_parameter_with_attribute)
RETURN_LONG(1);
}
static ZEND_FUNCTION(zend_get_map_ptr_last)
{
ZEND_PARSE_PARAMETERS_NONE();
RETURN_LONG(CG(map_ptr_last));
}
static zend_object *zend_test_class_new(zend_class_entry *class_type)
{
zend_object *obj = zend_objects_new(class_type);

View file

@ -164,6 +164,8 @@ namespace {
function zend_test_zend_ini_parse_uquantity(string $str): int {}
function zend_test_zend_ini_str(): string {}
function zend_get_map_ptr_last(): int {}
}
namespace ZendTestNS {

View file

@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: 2e96505b9e992f7e3976e52f4a7fed0c62f274e3 */
* Stub hash: a542841a2a34886c1ff4dc2154c322b5efbde35a */
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0)
ZEND_END_ARG_INFO()
@ -97,6 +97,9 @@ ZEND_END_ARG_INFO()
#define arginfo_zend_test_zend_ini_str arginfo_zend_get_current_func_name
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_get_map_ptr_last, 0, 0, IS_LONG, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ZendTestNS2_namespaced_func, 0, 0, _IS_BOOL, 0)
ZEND_END_ARG_INFO()
@ -114,8 +117,7 @@ ZEND_END_ARG_INFO()
#define arginfo_ZendTestNS2_ZendSubNS_namespaced_deprecated_aliased_func arginfo_zend_test_void_return
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class__ZendTestClass_is_object, 0, 0, IS_LONG, 0)
ZEND_END_ARG_INFO()
#define arginfo_class__ZendTestClass_is_object arginfo_zend_get_map_ptr_last
#define arginfo_class__ZendTestClass___toString arginfo_zend_get_current_func_name
@ -185,6 +187,7 @@ static ZEND_FUNCTION(zend_call_method);
static ZEND_FUNCTION(zend_test_zend_ini_parse_quantity);
static ZEND_FUNCTION(zend_test_zend_ini_parse_uquantity);
static ZEND_FUNCTION(zend_test_zend_ini_str);
static ZEND_FUNCTION(zend_get_map_ptr_last);
static ZEND_FUNCTION(ZendTestNS2_namespaced_func);
static ZEND_FUNCTION(ZendTestNS2_namespaced_deprecated_func);
static ZEND_FUNCTION(ZendTestNS2_ZendSubNS_namespaced_func);
@ -235,6 +238,7 @@ static const zend_function_entry ext_functions[] = {
ZEND_FE(zend_test_zend_ini_parse_quantity, arginfo_zend_test_zend_ini_parse_quantity)
ZEND_FE(zend_test_zend_ini_parse_uquantity, arginfo_zend_test_zend_ini_parse_uquantity)
ZEND_FE(zend_test_zend_ini_str, arginfo_zend_test_zend_ini_str)
ZEND_FE(zend_get_map_ptr_last, arginfo_zend_get_map_ptr_last)
ZEND_NS_FALIAS("ZendTestNS2", namespaced_func, ZendTestNS2_namespaced_func, arginfo_ZendTestNS2_namespaced_func)
ZEND_NS_DEP_FALIAS("ZendTestNS2", namespaced_deprecated_func, ZendTestNS2_namespaced_deprecated_func, arginfo_ZendTestNS2_namespaced_deprecated_func)
ZEND_NS_FALIAS("ZendTestNS2", namespaced_aliased_func, zend_test_void_return, arginfo_ZendTestNS2_namespaced_aliased_func)

View file

@ -0,0 +1,52 @@
--TEST--
GH-8646 (Memory leak PHP FPM 8.1)
--EXTENSIONS--
zend_test
--SKIPIF--
<?php include "skipif.inc"; ?>
--FILE--
<?php
require_once "tester.inc";
$cfg = <<<EOT
[global]
error_log = {{FILE:LOG}}
[unconfined]
listen = {{ADDR}}
pm = dynamic
pm.max_children = 5
pm.start_servers = 1
pm.min_spare_servers = 1
pm.max_spare_servers = 3
EOT;
$code = <<<EOT
<?php
class MyClass {}
echo zend_get_map_ptr_last();
EOT;
$tester = new FPM\Tester($cfg, $code);
$tester->start();
$tester->expectLogStartNotices();
$map_ptr_last_values = [];
for ($i = 0; $i < 10; $i++) {
$map_ptr_last_values[] = (int) $tester->request()->getBody();
}
// Ensure that map_ptr_last did not increase
var_dump(count(array_unique($map_ptr_last_values, SORT_REGULAR)) === 1);
$tester->terminate();
$tester->expectLogTerminatingNotices();
$tester->close();
?>
Done
--EXPECT--
bool(true)
Done
--CLEAN--
<?php
require_once "tester.inc";
FPM\Tester::clean();
?>