Download | Plain Text | Line Numbers


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;