diff -Naur a/main/main.c b/main/main.c --- a/main/main.c 2025-09-19 10:09:39.287855993 +0200 +++ b/main/main.c 2025-09-19 10:10:10.332649067 +0200 @@ -320,6 +320,16 @@ } else { value = Z_L(1)<<30; /* effectively, no limit */ } + + /* If memory_limit exceeds max_memory_limit, set to max_memory_limit instead. */ + if (value > PG(max_memory_limit)) { + zend_ini_entry *max_mem_limit_ini = zend_hash_str_find_ptr(EG(ini_directives), ZEND_STRL("max_memory_limit")); + entry->value = zend_string_init(ZSTR_VAL(max_mem_limit_ini->value), ZSTR_LEN(max_mem_limit_ini->value), true); + PG(memory_limit) = PG(max_memory_limit); + + return SUCCESS; + } + if (zend_set_memory_limit(value) == FAILURE) { /* When the memory limit is reset to the original level during deactivation, we may be * using more memory than the original limit while shutdown is still in progress. @@ -335,6 +345,26 @@ } /* }}} */ +static PHP_INI_MH(OnChangeMaxMemoryLimit) +{ + size_t value; + if (new_value) { + value = zend_ini_parse_uquantity_warn(new_value, entry->name); + } else { + value = Z_L(1) << 30; /* effectively, no limit */ + } + + if (zend_set_memory_limit(value) == FAILURE) { + zend_error(E_ERROR, "Failed to set memory limit to %zd bytes (Current memory usage is %zd bytes)", value, zend_memory_usage(true)); + return FAILURE; + } + + PG(max_memory_limit) = value; + zend_alter_ini_entry(ZSTR_INIT_LITERAL("memory_limit", 1), new_value, PHP_INI_ALL, stage); + + return SUCCESS; +} + /* {{{ PHP_INI_MH */ static PHP_INI_MH(OnSetLogFilter) { @@ -798,7 +828,10 @@ STD_PHP_INI_BOOLEAN("mail.mixed_lf_and_crlf", "0", PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateBool, mail_mixed_lf_and_crlf, php_core_globals, core_globals) STD_PHP_INI_ENTRY("mail.log", NULL, PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateMailLog, mail_log, php_core_globals, core_globals) PHP_INI_ENTRY("browscap", NULL, PHP_INI_SYSTEM, OnChangeBrowscap) - PHP_INI_ENTRY("memory_limit", "128M", PHP_INI_ALL, OnChangeMemoryLimit) + + PHP_INI_ENTRY("max_memory_limit", "-1", PHP_INI_SYSTEM, OnChangeMaxMemoryLimit) + PHP_INI_ENTRY("memory_limit", "128M", PHP_INI_ALL, OnChangeMemoryLimit) + PHP_INI_ENTRY("precision", "14", PHP_INI_ALL, OnSetPrecision) PHP_INI_ENTRY("sendmail_from", NULL, PHP_INI_ALL, NULL) PHP_INI_ENTRY("sendmail_path", DEFAULT_SENDMAIL_PATH, PHP_INI_SYSTEM, NULL) diff -Naur a/main/php_globals.h b/main/php_globals.h --- a/main/php_globals.h 2025-09-19 10:09:39.139856979 +0200 +++ b/main/php_globals.h 2025-09-19 10:10:10.333649061 +0200 @@ -72,6 +72,7 @@ zend_long serialize_precision; zend_long memory_limit; + zend_long max_memory_limit; zend_long max_input_time; char *error_log; diff -Naur a/php.ini-development b/php.ini-development --- a/php.ini-development 2025-09-19 10:09:39.140856972 +0200 +++ b/php.ini-development 2025-09-19 10:10:10.333649061 +0200 @@ -435,6 +435,7 @@ ; Maximum amount of memory a script may consume ; https://php.net/memory-limit memory_limit = 128M +max_memory_limit = -1 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Error handling and logging ; diff -Naur a/php.ini-production b/php.ini-production --- a/php.ini-production 2025-09-19 10:09:39.140856972 +0200 +++ b/php.ini-production 2025-09-19 10:10:10.334649054 +0200 @@ -437,6 +437,7 @@ ; Maximum amount of memory a script may consume ; https://php.net/memory-limit memory_limit = 128M +max_memory_limit = -1 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Error handling and logging ; diff -Naur a/Zend/zend_ini.c b/Zend/zend_ini.c --- a/Zend/zend_ini.c 2025-08-26 15:36:28.000000000 +0200 +++ b/Zend/zend_ini.c 2025-09-19 10:10:17.054605510 +0200 @@ -247,10 +247,16 @@ zend_unregister_ini_entries_ex(module_number, module_type); return FAILURE; } + + zend_string *prev_value = p->value; + if (((default_value = zend_get_configuration_directive(p->name)) != NULL) && (!p->on_modify || p->on_modify(p, Z_STR_P(default_value), p->mh_arg1, p->mh_arg2, p->mh_arg3, ZEND_INI_STAGE_STARTUP) == SUCCESS)) { - p->value = zend_new_interned_string(zend_string_copy(Z_STR_P(default_value))); + /* Skip assigning the value if the handler has already done so. */ + if (p->value == prev_value) { + p->value = zend_new_interned_string(zend_string_copy(Z_STR_P(default_value))); + } } else { p->value = ini_entry->value ? zend_string_init_interned(ini_entry->value, ini_entry->value_length, 1) : NULL; @@ -388,14 +394,20 @@ zend_hash_add_ptr(EG(modified_ini_directives), ini_entry->name, ini_entry); } + zend_string *prev_value = ini_entry->value; duplicate = zend_string_copy(new_value); if (!ini_entry->on_modify || ini_entry->on_modify(ini_entry, duplicate, ini_entry->mh_arg1, ini_entry->mh_arg2, ini_entry->mh_arg3, stage) == SUCCESS) { - if (modified && ini_entry->orig_value != ini_entry->value) { /* we already changed the value, free the changed value */ - zend_string_release(ini_entry->value); + if (modified && ini_entry->orig_value != prev_value) { /* we already changed the value, free the changed value */ + zend_string_release(prev_value); + } + /* Skip assigning the value if the handler has already done so. */ + if (ini_entry->value == prev_value) { + ini_entry->value = duplicate; + } else { + zend_string_release(duplicate); } - ini_entry->value = duplicate; } else { zend_string_release(duplicate); return FAILURE;