Hi,
I have ported the official minimal fix for CVE-2026-109618 and
CVE-2026-109619 (php-src commit 712e60f9ed, "Avoid state corruption
via bailout", the PHP 7.4 backport) to the PHP 5 packages of
Enterprise Linux 6 and 7, which will not get a vendor update:
php-5.4.16-48.el7 (RHEL / CentOS / Oracle Linux / Scientific
Linux 7) -> php-5.4.16-48.el7.php2shell.1
php-5.3.3-50.el6_10 (RHEL / CentOS / Oracle Linux 6)
-> php-5.3.3-50.el6_10.php2shell.1
The patches are made against the vendor source trees (after %prep,
with all vendor patches applied) and are attached. The spec file
changes, a container build and CI are in:
https://github.com/alvm/php2shell-eol-backport
This adds the EL6 / EL7 vendor packages to the unofficial backports
that Leon Lai announced for Amazon Linux and for PHP 7.3 on EL7:
https://www.openwall.com/lists/oss-security/2026/10/22/13
What the 7.4 fix maps to in the PHP 5 engine
--------------------------------------------
Ported directly to 5.4.16 and 5.3.3:
- Allocation failures no longer bail out. zend_mm_safe_error() and
the "Possible integer overflow in memory allocation" check in the
four safe_address() variants print the error to stderr and call
_exit(1), through a new zend_mm_safe_error_ex(). zend_mm_panic()
uses _exit() as well.
- The same exit path is used for both Balloc() errors in
zend_strtod.c and for the string size overflow in
concat_function(), the PHP 5 counterpart of the ZEND_CONCAT /
ZEND_FAST_CONCAT checks. The second branch of concat_function()
also gets the size check that was missing there.
- Request state is reset: sapi_deactivate() sets auth_user,
auth_password, auth_digest, content_type_dup and current_user to
NULL; zend_llist_destroy() resets head and tail;
shutdown_executor() resets EG(error_handling) and EG(exception);
php_request_shutdown() clears PG(http_globals) (PHP 5 otherwise
clears them only at the next request startup) and resets
PG(modules_activated).
- Multipart parsing keeps only the Content-Disposition and
Content-Type part headers, ignores duplicates, and limits the
number of parts with max_multipart_body_parts (default:
max_input_vars + max_file_uploads).
Session upload progress is disabled in code in 5.4, as in the 7.4
backport. PHP 5.3 does not have the feature.
Not applicable to PHP 5: the zend_hash changes (the PHP 5 HashTable
grows through perealloc_recoverable() and has no fatal error path
there), the VM handler changes and the single-slot superglobal
arrays.
Notes for anyone porting to other PHP 5 trees
---------------------------------------------
- Later PHP 5 releases have more fatal allocation paths than these
vendor trees: php-5.4.45 has two "String size overflow" sites in
zend_operators.c, and php-5.6.40 has six "Possible integer
overflow" call sites in zend_alloc.c instead of four. A port that
converts only the call sites known from 5.4.16 misses them.
- Compared with Leon Lai's independent port to the Amazon Linux 2
php-5.4.16 package (derived from the RHEL 7 one), the core changes
are identical line for line. That port does not include the
concat, Balloc(), zend_mm_panic() and max_multipart_body_parts
parts; it also clears http_globals in
php_request_shutdown_for_hook(), which no SAPI of the RHEL build
calls. Details:
https://github.com/alvm/php2shell-eol-backport/blob/main/docs/ALTERNATIVES.md
Risk on PHP 5
-------------
As of 22 Oct, Red Hat's VEX lists the RHEL 6 and 7 php packages as
"under investigation" for CVE-2026-109618 and as not affected by
CVE-2026-109619 ("vulnerable code not present"). An independent
analysis of PHP 5.3 [1] found that the out-of-memory fault can be
reached, but the published memory corruption step relies on PHP 7
engine internals, so on PHP 5 the known attack ends in a denial of
service; 5.4 to 5.6 were assessed by comparing the code only.
The backport applies the mitigation anyway, because it removes the
unsafe error path itself, not only the known way to reach it.
[1] Analysis of php2shell on PHP 5.3.15:
https://github.com/euriconicacio/blog/blob/main/all_collections/_posts/2026-10-22-evaluating-php2shell-reachability-and-corruptibility-in-php-5-3-15.md
Behaviour changes
-----------------
- Exceeding memory_limit ends the process. "Fatal error: ..." goes
to stderr (the web server error log), shutdown functions do not
run, and temporary files of uploads in that request stay in /tmp
until tmpwatch / systemd-tmpfiles removes them. This follows the
7.4 fix.
- Session upload progress does not work (5.4).
- New php.ini directive max_multipart_body_parts.
- ABI: one new exported function (zend_mm_safe_error_ex); existing
structures and functions are unchanged.
Testing
-------
The packages were rebuilt from the vendor SRPMs (pinned SHA-256) in
pinned Oracle Linux containers, and the test suite of the RPM %check
was compared with an unmodified rebuild of the same SRPM:
EL7, 5.4.16: 12373 tests, failures 14 -> 23
(8 session upload progress tests and
tests/lang/bug45392.phpt)
EL6, 5.3.3: 11213 tests, failures 55 -> 56
(tests/lang/bug45392.phpt: the fatal error message
now starts with an empty line, as with the 7.4 fix)
GitHub Actions rebuilds both packages on every push with the same
results, apart from a few environment-dependent tests (SPL file
owner tests, a timing test) that come and go between runners. No
exploit code was written or used.
Packages
--------
Signed RPMs and a yum repository, for those who prefer not to build
the packages themselves:
https://github.com/alvm/php2shell-eol-backport/releases/tag/v1
https://alvm.github.io/php2shell-eol-backport/
Signing key: 2757 B60F D23D ED25 021B BE37 8079 7C97 A561 BC94
These are unofficial and come without support. TuxCare's commercial
ELS also shipped fixed builds of these packages on 21 Oct. Moving to
a supported PHP version remains the real fix.
Attached patches (SHA-256):
php-5.4.16-48.el7-cve-2026-109618.patch
cefd460a8a3c73e4ccd42ae478e3cb944e19f713020983e0358878c3a0a7a33e
php-5.3.3-50.el6_10-cve-2026-109618.patch
3f47e82abb08d2a84f945d7fe76eb59a2fbcff2305c2e415baedd26dab9f0eae
--
Oleksii Molchanov
diff -ruN a/ext/session/session.c b/ext/session/session.c
--- a/ext/session/session.c 2026-10-22 19:39:12.000000000 +0300
+++ b/ext/session/session.c 2026-10-22 20:44:11.491272744 +0300
@@ -2393,7 +2393,8 @@
if (php_session_rfc1867_orig_callback) {
retval = php_session_rfc1867_orig_callback(event, event_data, extra TSRMLS_CC);
}
- if (!PS(rfc1867_enabled)) {
+ /* CVE-2026-109618: session upload progress is disabled (CVE-2026-109619) */
+ if (!PS(rfc1867_enabled) || 1) {
return retval;
}
diff -ruN a/main/main.c b/main/main.c
--- a/main/main.c 2026-10-22 19:39:12.000000000 +0300
+++ b/main/main.c 2026-10-22 20:44:11.491272744 +0300
@@ -553,6 +553,7 @@
PHP_INI_ENTRY("disable_functions", "", PHP_INI_SYSTEM, NULL)
PHP_INI_ENTRY("disable_classes", "", PHP_INI_SYSTEM, NULL)
PHP_INI_ENTRY("max_file_uploads", "20", PHP_INI_SYSTEM|PHP_INI_PERDIR, NULL)
+ PHP_INI_ENTRY("max_multipart_body_parts", "-1", PHP_INI_SYSTEM|PHP_INI_PERDIR, NULL)
STD_PHP_INI_BOOLEAN("allow_url_fopen", "1", PHP_INI_SYSTEM, OnUpdateBool, allow_url_fopen, php_core_globals, core_globals)
STD_PHP_INI_BOOLEAN("allow_url_include", "0", PHP_INI_SYSTEM, OnUpdateBool, allow_url_include, php_core_globals, core_globals)
@@ -1835,6 +1836,10 @@
#ifdef HAVE_DTRACE
DTRACE_REQUEST_SHUTDOWN(SAFE_FILENAME(SG(request_info).path_translated), SAFE_FILENAME(SG(request_info).request_uri), SAFE_FILENAME(SG(request_info).request_method));
#endif /* HAVE_DTRACE */
+
+ /* CVE-2026-109618: may be accessed before php_request_startup() of the next request */
+ memset(PG(http_globals), 0, sizeof(PG(http_globals)));
+ PG(modules_activated) = 0;
}
/* }}} */
diff -ruN a/main/rfc1867.c b/main/rfc1867.c
--- a/main/rfc1867.c 2026-10-22 19:39:12.000000000 +0300
+++ b/main/rfc1867.c 2026-10-22 20:44:11.491272744 +0300
@@ -395,6 +395,14 @@
return 0;
}
+static zend_bool php_mime_should_save_hdr(const char *name)
+{
+ return !strcasecmp(name, "Content-Disposition")
+ || !strcasecmp(name, "Content-Type");
+}
+
+static char *php_mime_get_hdr_value(zend_llist header, char *key);
+
/* parse headers */
static int multipart_buffer_headers(multipart_buffer *self, zend_llist *header TSRMLS_DC)
{
@@ -436,10 +444,16 @@
}
*value = '\0';
- do { value++; } while(isspace(*value));
- key = estrdup(line);
- smart_str_appends(&buf_value, value);
+ /* CVE-2026-109618: keep only headers we use, and ignore duplicates */
+ if (!php_mime_should_save_hdr(line)
+ || php_mime_get_hdr_value(*header, line)) {
+ key = NULL;
+ } else {
+ key = estrdup(line);
+ do { value++; } while (isspace((unsigned char)*value));
+ smart_str_appends(&buf_value, value);
+ }
} else if (buf_value.c) { /* If no ':' on the line, add to previous line */
smart_str_appends(&buf_value, line);
} else {
@@ -690,6 +704,8 @@
void *event_extra_data = NULL;
unsigned int llen = 0;
int upload_cnt = INI_INT("max_file_uploads");
+ int body_parts_cnt = INI_INT("max_multipart_body_parts");
+ int body_parts_limit;
const zend_encoding *internal_encoding = zend_multibyte_get_internal_encoding(TSRMLS_C);
php_rfc1867_getword_t getword;
php_rfc1867_getword_conf_t getword_conf;
@@ -711,6 +727,12 @@
return;
}
+ /* CVE-2026-109618: default limit of multipart body parts */
+ if (body_parts_cnt < 0) {
+ body_parts_cnt = PG(max_input_vars) + upload_cnt;
+ }
+ body_parts_limit = body_parts_cnt;
+
/* Get the boundary */
boundary = strstr(content_type_dup, "boundary");
if (!boundary) {
@@ -795,6 +817,11 @@
char *pair = NULL;
int end = 0;
+ if (--body_parts_cnt < 0) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Multipart body parts limit exceeded %d. To increase the limit change max_multipart_body_parts in php.ini.", body_parts_limit);
+ goto fileupload_done;
+ }
+
while (isspace(*cd)) {
++cd;
}
diff -ruN a/main/SAPI.c b/main/SAPI.c
--- a/main/SAPI.c 2026-10-22 19:39:12.000000000 +0300
+++ b/main/SAPI.c 2026-10-22 20:44:11.491272744 +0300
@@ -519,18 +519,23 @@
}
if (SG(request_info).auth_user) {
efree(SG(request_info).auth_user);
+ SG(request_info).auth_user = NULL;
}
if (SG(request_info).auth_password) {
efree(SG(request_info).auth_password);
+ SG(request_info).auth_password = NULL;
}
if (SG(request_info).auth_digest) {
efree(SG(request_info).auth_digest);
+ SG(request_info).auth_digest = NULL;
}
if (SG(request_info).content_type_dup) {
efree(SG(request_info).content_type_dup);
+ SG(request_info).content_type_dup = NULL;
}
if (SG(request_info).current_user) {
efree(SG(request_info).current_user);
+ SG(request_info).current_user = NULL;
}
if (sapi_module.deactivate) {
sapi_module.deactivate(TSRMLS_C);
diff -ruN a/Zend/zend_alloc.c b/Zend/zend_alloc.c
--- a/Zend/zend_alloc.c 2026-10-22 19:39:12.000000000 +0300
+++ b/Zend/zend_alloc.c 2026-10-22 20:44:11.491272744 +0300
@@ -91,7 +91,7 @@
#if ZEND_DEBUG && defined(HAVE_KILL) && defined(HAVE_GETPID)
kill(getpid(), SIGSEGV);
#endif
- exit(1);
+ _exit(1);
}
/*******************/
@@ -1736,6 +1736,40 @@
}
}
+ZEND_API void zend_mm_safe_error_ex(const char *format, ...)
+{
+ const char *error_filename;
+ uint error_lineno;
+ char buf[1024];
+ va_list args;
+ TSRMLS_FETCH();
+
+ if (zend_is_compiling(TSRMLS_C)) {
+ error_filename = zend_get_compiled_filename(TSRMLS_C);
+ error_lineno = zend_get_compiled_lineno(TSRMLS_C);
+ } else if (EG(in_execution)) {
+ error_filename = EG(active_op_array)?EG(active_op_array)->filename:NULL;
+ error_lineno = EG(opline_ptr)?(*EG(opline_ptr))->lineno:0;
+ } else {
+ error_filename = NULL;
+ error_lineno = 0;
+ }
+ if (!error_filename) {
+ error_filename = "Unknown";
+ }
+
+ va_start(args, format);
+ vsnprintf(buf, sizeof(buf), format, args);
+ va_end(args);
+
+ fprintf(stderr, "\nFatal error: %s in %s on line %d\n", buf, error_filename, error_lineno);
+/* See http://support.microsoft.com/kb/190351 */
+#ifdef PHP_WIN32
+ fflush(stderr);
+#endif
+ _exit(1);
+}
+
static void zend_mm_safe_error(zend_mm_heap *heap,
const char *format,
size_t limit,
@@ -1745,59 +1779,14 @@
#endif
size_t size)
{
- if (heap->reserve) {
- _zend_mm_free_int(heap, heap->reserve ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC);
- heap->reserve = NULL;
- }
- if (heap->overflow == 0) {
- const char *error_filename;
- uint error_lineno;
- TSRMLS_FETCH();
- if (zend_is_compiling(TSRMLS_C)) {
- error_filename = zend_get_compiled_filename(TSRMLS_C);
- error_lineno = zend_get_compiled_lineno(TSRMLS_C);
- } else if (EG(in_execution)) {
- error_filename = EG(active_op_array)?EG(active_op_array)->filename:NULL;
- error_lineno = EG(opline_ptr)?(*EG(opline_ptr))->lineno:0;
- } else {
- error_filename = NULL;
- error_lineno = 0;
- }
- if (!error_filename) {
- error_filename = "Unknown";
- }
- heap->overflow = 1;
- zend_try {
- zend_error_noreturn(E_ERROR,
- format,
- limit,
-#if ZEND_DEBUG
- filename,
- lineno,
-#endif
- size);
- } zend_catch {
- if (heap->overflow == 2) {
- fprintf(stderr, "\nFatal error: ");
- fprintf(stderr,
- format,
- limit,
+ (void) heap;
+ zend_mm_safe_error_ex(format,
+ limit,
#if ZEND_DEBUG
- filename,
- lineno,
+ filename,
+ lineno,
#endif
- size);
- fprintf(stderr, " in %s on line %d\n", error_filename, error_lineno);
- }
-/* See http://support.microsoft.com/kb/190351 */
-#ifdef PHP_WIN32
- fflush(stderr);
-#endif
- } zend_end_try();
- } else {
- heap->overflow = 2;
- }
- zend_bailout();
+ size);
}
static zend_mm_free_block *zend_mm_search_large_block(zend_mm_heap *heap, size_t true_size)
@@ -2468,7 +2457,7 @@
"rm"(offset));
if (UNEXPECTED(overflow)) {
- zend_error_noreturn(E_ERROR, "Possible integer overflow in memory allocation (%zu * %zu + %zu)", nmemb, size, offset);
+ zend_mm_safe_error_ex("Possible integer overflow in memory allocation (%zu * %zu + %zu)", nmemb, size, offset);
return 0;
}
return res;
@@ -2498,7 +2487,7 @@
#undef LP_SUFF
if (UNEXPECTED(overflow)) {
- zend_error_noreturn(E_ERROR, "Possible integer overflow in memory allocation (%zu * %zu + %zu)", nmemb, size, offset);
+ zend_mm_safe_error_ex("Possible integer overflow in memory allocation (%zu * %zu + %zu)", nmemb, size, offset);
return 0;
}
return res;
@@ -2511,7 +2500,7 @@
zend_ulong64 res = (zend_ulong64)nmemb * (zend_ulong64)size + (zend_ulong64)offset;
if (UNEXPECTED(res > (zend_ulong64)0xFFFFFFFFL)) {
- zend_error_noreturn(E_ERROR, "Possible integer overflow in memory allocation (%zu * %zu + %zu)", nmemb, size, offset);
+ zend_mm_safe_error_ex("Possible integer overflow in memory allocation (%zu * %zu + %zu)", nmemb, size, offset);
return 0;
}
return (size_t) res;
@@ -2526,7 +2515,7 @@
double _delta = (double)res - _d;
if (UNEXPECTED((_d + _delta ) != _d)) {
- zend_error_noreturn(E_ERROR, "Possible integer overflow in memory allocation (%zu * %zu + %zu)", nmemb, size, offset);
+ zend_mm_safe_error_ex("Possible integer overflow in memory allocation (%zu * %zu + %zu)", nmemb, size, offset);
return 0;
}
return res;
diff -ruN a/Zend/zend_alloc.h b/Zend/zend_alloc.h
--- a/Zend/zend_alloc.h 2026-10-22 19:39:12.000000000 +0300
+++ b/Zend/zend_alloc.h 2026-10-22 20:44:11.491272744 +0300
@@ -159,6 +159,15 @@
ZEND_API size_t zend_memory_usage(int real_usage TSRMLS_DC);
ZEND_API size_t zend_memory_peak_usage(int real_usage TSRMLS_DC);
+/* CVE-2026-109618: report a fatal allocation error and terminate the process at once,
+ * without zend_bailout(): request memory may be in an inconsistent state
+ * (CVE-2026-109618). */
+ZEND_API void zend_mm_safe_error_ex(const char *format, ...)
+#if (defined (__GNUC__) && __GNUC__ > 2 ) && !defined(__INTEL_COMPILER) && !defined(DARWIN) && !defined(__hpux) && !defined(_AIX)
+ __attribute__ ((noreturn))
+#endif
+ ;
+
END_EXTERN_C()
/* fast cache for zval's */
diff -ruN a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c
--- a/Zend/zend_execute_API.c 2026-10-22 19:39:12.000000000 +0300
+++ b/Zend/zend_execute_API.c 2026-10-22 20:44:11.491272744 +0300
@@ -294,6 +294,10 @@
zend_objects_store_free_object_storage(&EG(objects_store) TSRMLS_CC);
+ /* CVE-2026-109618: may be used by zend_error() before init_executor() */
+ EG(error_handling) = EH_NORMAL;
+ EG(exception) = NULL;
+
/* Destroy all op arrays */
if (EG(full_tables_cleanup)) {
zend_hash_reverse_apply(EG(function_table), (apply_func_t) clean_non_persistent_function_full TSRMLS_CC);
diff -ruN a/Zend/zend_llist.c b/Zend/zend_llist.c
--- a/Zend/zend_llist.c 2026-10-22 19:39:12.000000000 +0300
+++ b/Zend/zend_llist.c 2026-10-22 20:44:11.491272744 +0300
@@ -117,6 +117,8 @@
current = next;
}
+ l->head = NULL;
+ l->tail = NULL;
l->count = 0;
}
diff -ruN a/Zend/zend_operators.c b/Zend/zend_operators.c
--- a/Zend/zend_operators.c 2026-10-22 19:39:12.000000000 +0300
+++ b/Zend/zend_operators.c 2026-10-22 20:44:11.491272744 +0300
@@ -1261,7 +1261,7 @@
if (Z_STRLEN_P(result) < 0 || (int) (Z_STRLEN_P(op1) + Z_STRLEN_P(op2)) < 0) {
efree(Z_STRVAL_P(result));
ZVAL_EMPTY_STRING(result);
- zend_error(E_ERROR, "String size overflow");
+ zend_mm_safe_error_ex("String size overflow");
}
Z_STRVAL_P(result) = erealloc(Z_STRVAL_P(result), res_len+1);
@@ -1270,8 +1270,15 @@
Z_STRVAL_P(result)[res_len]=0;
Z_STRLEN_P(result) = res_len;
} else {
- int length = Z_STRLEN_P(op1) + Z_STRLEN_P(op2);
- char *buf = (char *) emalloc(length + 1);
+ int length;
+ char *buf;
+
+ /* CVE-2026-109618: the sum must fit in int, including the trailing NUL */
+ if (UNEXPECTED(Z_STRLEN_P(op1) < 0 || Z_STRLEN_P(op2) < 0 || Z_STRLEN_P(op1) > INT_MAX - 1 - Z_STRLEN_P(op2))) {
+ zend_mm_safe_error_ex("String size overflow");
+ }
+ length = Z_STRLEN_P(op1) + Z_STRLEN_P(op2);
+ buf = (char *) emalloc(length + 1);
memcpy(buf, Z_STRVAL_P(op1), Z_STRLEN_P(op1));
memcpy(buf + Z_STRLEN_P(op1), Z_STRVAL_P(op2), Z_STRLEN_P(op2));
diff -ruN a/Zend/zend_strtod.c b/Zend/zend_strtod.c
--- a/Zend/zend_strtod.c 2026-10-22 19:39:12.000000000 +0300
+++ b/Zend/zend_strtod.c 2026-10-22 20:44:11.491272744 +0300
@@ -480,7 +480,7 @@
Bigint *rv;
if (k > Kmax) {
- zend_error(E_ERROR, "Balloc() allocation exceeds list boundary");
+ zend_mm_safe_error_ex("Balloc() allocation exceeds list boundary");
}
_THREAD_PRIVATE_MUTEX_LOCK(dtoa_mutex);
@@ -491,7 +491,7 @@
rv = (Bigint *)MALLOC(sizeof(Bigint) + (x-1)*sizeof(Long));
if (!rv) {
_THREAD_PRIVATE_MUTEX_UNLOCK(dtoa_mutex);
- zend_error(E_ERROR, "Balloc() failed to allocate memory");
+ zend_mm_safe_error_ex("Balloc() failed to allocate memory");
}
rv->k = k;
rv->maxwds = x;
diff -ruN a/main/main.c b/main/main.c
--- a/main/main.c 2026-10-22 19:39:17.000000000 +0300
+++ b/main/main.c 2026-10-22 20:44:12.699275643 +0300
@@ -518,6 +518,7 @@
PHP_INI_ENTRY("disable_functions", "", PHP_INI_SYSTEM, NULL)
PHP_INI_ENTRY("disable_classes", "", PHP_INI_SYSTEM, NULL)
PHP_INI_ENTRY("max_file_uploads", "20", PHP_INI_SYSTEM, NULL)
+ PHP_INI_ENTRY("max_multipart_body_parts", "-1", PHP_INI_SYSTEM|PHP_INI_PERDIR, NULL)
STD_PHP_INI_BOOLEAN("allow_url_fopen", "1", PHP_INI_SYSTEM, OnUpdateBool, allow_url_fopen, php_core_globals, core_globals)
STD_PHP_INI_BOOLEAN("allow_url_include", "0", PHP_INI_SYSTEM, OnUpdateBool, allow_url_include, php_core_globals, core_globals)
@@ -1671,6 +1672,10 @@
PG(com_initialized) = 0;
}
#endif
+
+ /* CVE-2026-109618: may be accessed before php_request_startup() of the next request */
+ memset(PG(http_globals), 0, sizeof(PG(http_globals)));
+ PG(modules_activated) = 0;
}
/* }}} */
diff -ruN a/main/rfc1867.c b/main/rfc1867.c
--- a/main/rfc1867.c 2026-10-22 19:39:17.000000000 +0300
+++ b/main/rfc1867.c 2026-10-22 20:44:12.699275643 +0300
@@ -459,6 +459,14 @@
return 0;
}
+static zend_bool php_mime_should_save_hdr(const char *name)
+{
+ return !strcasecmp(name, "Content-Disposition")
+ || !strcasecmp(name, "Content-Type");
+}
+
+static char *php_mime_get_hdr_value(zend_llist header, char *key);
+
/* parse headers */
static int multipart_buffer_headers(multipart_buffer *self, zend_llist *header TSRMLS_DC)
{
@@ -496,10 +504,16 @@
}
*value = '\0';
- do { value++; } while(isspace(*value));
- key = estrdup(line);
- smart_str_appends(&buf_value, value);
+ /* CVE-2026-109618: keep only headers we use, and ignore duplicates */
+ if (!php_mime_should_save_hdr(line)
+ || php_mime_get_hdr_value(*header, line)) {
+ key = NULL;
+ } else {
+ key = estrdup(line);
+ do { value++; } while (isspace((unsigned char)*value));
+ smart_str_appends(&buf_value, value);
+ }
} else if (buf_value.c) { /* If no ':' on the line, add to previous line */
smart_str_appends(&buf_value, line);
} else {
@@ -782,12 +796,20 @@
void *event_extra_data = NULL;
int llen = 0;
int upload_cnt = INI_INT("max_file_uploads");
+ int body_parts_cnt = INI_INT("max_multipart_body_parts");
+ int body_parts_limit;
if (SG(post_max_size) > 0 && SG(request_info).content_length > SG(post_max_size)) {
sapi_module.sapi_error(E_WARNING, "POST Content-Length of %ld bytes exceeds the limit of %ld bytes", SG(request_info).content_length, SG(post_max_size));
return;
}
+ /* CVE-2026-109618: default limit of multipart body parts */
+ if (body_parts_cnt < 0) {
+ body_parts_cnt = PG(max_input_vars) + upload_cnt;
+ }
+ body_parts_limit = body_parts_cnt;
+
/* Get the boundary */
boundary = strstr(content_type_dup, "boundary");
if (!boundary) {
@@ -878,6 +900,11 @@
char *pair = NULL;
int end = 0;
+ if (--body_parts_cnt < 0) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Multipart body parts limit exceeded %d. To increase the limit change max_multipart_body_parts in php.ini.", body_parts_limit);
+ goto fileupload_done;
+ }
+
while (isspace(*cd)) {
++cd;
}
diff -ruN a/main/SAPI.c b/main/SAPI.c
--- a/main/SAPI.c 2026-10-22 19:39:17.000000000 +0300
+++ b/main/SAPI.c 2026-10-22 20:44:12.699275643 +0300
@@ -427,18 +427,23 @@
}
if (SG(request_info).auth_user) {
efree(SG(request_info).auth_user);
+ SG(request_info).auth_user = NULL;
}
if (SG(request_info).auth_password) {
efree(SG(request_info).auth_password);
+ SG(request_info).auth_password = NULL;
}
if (SG(request_info).auth_digest) {
efree(SG(request_info).auth_digest);
+ SG(request_info).auth_digest = NULL;
}
if (SG(request_info).content_type_dup) {
efree(SG(request_info).content_type_dup);
+ SG(request_info).content_type_dup = NULL;
}
if (SG(request_info).current_user) {
efree(SG(request_info).current_user);
+ SG(request_info).current_user = NULL;
}
if (sapi_module.deactivate) {
sapi_module.deactivate(TSRMLS_C);
diff -ruN a/Zend/zend_alloc.c b/Zend/zend_alloc.c
--- a/Zend/zend_alloc.c 2026-10-22 19:39:17.000000000 +0300
+++ b/Zend/zend_alloc.c 2026-10-22 20:44:12.699275643 +0300
@@ -91,7 +91,7 @@
#if ZEND_DEBUG && defined(HAVE_KILL) && defined(HAVE_GETPID)
kill(getpid(), SIGSEGV);
#endif
- exit(1);
+ _exit(1);
}
/*******************/
@@ -1660,6 +1660,40 @@
}
}
+ZEND_API void zend_mm_safe_error_ex(const char *format, ...)
+{
+ const char *error_filename;
+ uint error_lineno;
+ char buf[1024];
+ va_list args;
+ TSRMLS_FETCH();
+
+ if (zend_is_compiling(TSRMLS_C)) {
+ error_filename = zend_get_compiled_filename(TSRMLS_C);
+ error_lineno = zend_get_compiled_lineno(TSRMLS_C);
+ } else if (EG(in_execution)) {
+ error_filename = EG(active_op_array)?EG(active_op_array)->filename:NULL;
+ error_lineno = EG(opline_ptr)?(*EG(opline_ptr))->lineno:0;
+ } else {
+ error_filename = NULL;
+ error_lineno = 0;
+ }
+ if (!error_filename) {
+ error_filename = "Unknown";
+ }
+
+ va_start(args, format);
+ vsnprintf(buf, sizeof(buf), format, args);
+ va_end(args);
+
+ fprintf(stderr, "\nFatal error: %s in %s on line %d\n", buf, error_filename, error_lineno);
+/* See http://support.microsoft.com/kb/190351 */
+#ifdef PHP_WIN32
+ fflush(stderr);
+#endif
+ _exit(1);
+}
+
static void zend_mm_safe_error(zend_mm_heap *heap,
const char *format,
size_t limit,
@@ -1669,59 +1703,14 @@
#endif
size_t size)
{
- if (heap->reserve) {
- _zend_mm_free_int(heap, heap->reserve ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC);
- heap->reserve = NULL;
- }
- if (heap->overflow == 0) {
- char *error_filename;
- uint error_lineno;
- TSRMLS_FETCH();
- if (zend_is_compiling(TSRMLS_C)) {
- error_filename = zend_get_compiled_filename(TSRMLS_C);
- error_lineno = zend_get_compiled_lineno(TSRMLS_C);
- } else if (EG(in_execution)) {
- error_filename = EG(active_op_array)?EG(active_op_array)->filename:NULL;
- error_lineno = EG(opline_ptr)?(*EG(opline_ptr))->lineno:0;
- } else {
- error_filename = NULL;
- error_lineno = 0;
- }
- if (!error_filename) {
- error_filename = "Unknown";
- }
- heap->overflow = 1;
- zend_try {
- zend_error_noreturn(E_ERROR,
- format,
- limit,
-#if ZEND_DEBUG
- filename,
- lineno,
-#endif
- size);
- } zend_catch {
- if (heap->overflow == 2) {
- fprintf(stderr, "\nFatal error: ");
- fprintf(stderr,
- format,
- limit,
+ (void) heap;
+ zend_mm_safe_error_ex(format,
+ limit,
#if ZEND_DEBUG
- filename,
- lineno,
+ filename,
+ lineno,
#endif
- size);
- fprintf(stderr, " in %s on line %d\n", error_filename, error_lineno);
- }
-/* See http://support.microsoft.com/kb/190351 */
-#ifdef PHP_WIN32
- fflush(stderr);
-#endif
- } zend_end_try();
- } else {
- heap->overflow = 2;
- }
- zend_bailout();
+ size);
}
static zend_mm_free_block *zend_mm_search_large_block(zend_mm_heap *heap, size_t true_size)
@@ -2383,7 +2372,7 @@
"rm"(offset));
if (UNEXPECTED(overflow)) {
- zend_error_noreturn(E_ERROR, "Possible integer overflow in memory allocation (%zu * %zu + %zu)", nmemb, size, offset);
+ zend_mm_safe_error_ex("Possible integer overflow in memory allocation (%zu * %zu + %zu)", nmemb, size, offset);
return 0;
}
return res;
@@ -2403,7 +2392,7 @@
"rm"(offset));
if (UNEXPECTED(overflow)) {
- zend_error_noreturn(E_ERROR, "Possible integer overflow in memory allocation (%zu * %zu + %zu)", nmemb, size, offset);
+ zend_mm_safe_error_ex("Possible integer overflow in memory allocation (%zu * %zu + %zu)", nmemb, size, offset);
return 0;
}
return res;
@@ -2416,7 +2405,7 @@
zend_ulong64 res = (zend_ulong64)nmemb * (zend_ulong64)size + (zend_ulong64)offset;
if (UNEXPECTED(res > (zend_ulong64)0xFFFFFFFFL)) {
- zend_error_noreturn(E_ERROR, "Possible integer overflow in memory allocation (%zu * %zu + %zu)", nmemb, size, offset);
+ zend_mm_safe_error_ex("Possible integer overflow in memory allocation (%zu * %zu + %zu)", nmemb, size, offset);
return 0;
}
return (size_t) res;
@@ -2431,7 +2420,7 @@
double _delta = (double)res - _d;
if (UNEXPECTED((_d + _delta ) != _d)) {
- zend_error_noreturn(E_ERROR, "Possible integer overflow in memory allocation (%zu * %zu + %zu)", nmemb, size, offset);
+ zend_mm_safe_error_ex("Possible integer overflow in memory allocation (%zu * %zu + %zu)", nmemb, size, offset);
return 0;
}
return res;
diff -ruN a/Zend/zend_alloc.h b/Zend/zend_alloc.h
--- a/Zend/zend_alloc.h 2026-10-22 19:39:17.000000000 +0300
+++ b/Zend/zend_alloc.h 2026-10-22 20:44:12.699275643 +0300
@@ -159,6 +159,15 @@
ZEND_API size_t zend_memory_usage(int real_usage TSRMLS_DC);
ZEND_API size_t zend_memory_peak_usage(int real_usage TSRMLS_DC);
+/* CVE-2026-109618: report a fatal allocation error and terminate the process at once,
+ * without zend_bailout(): request memory may be in an inconsistent state
+ * (CVE-2026-109618). */
+ZEND_API void zend_mm_safe_error_ex(const char *format, ...)
+#if (defined (__GNUC__) && __GNUC__ > 2 ) && !defined(__INTEL_COMPILER) && !defined(DARWIN) && !defined(__hpux) && !defined(_AIX)
+ __attribute__ ((noreturn))
+#endif
+ ;
+
END_EXTERN_C()
/* Macroses for zend_fast_cache.h compatibility */
diff -ruN a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c
--- a/Zend/zend_execute_API.c 2026-10-22 19:39:17.000000000 +0300
+++ b/Zend/zend_execute_API.c 2026-10-22 20:44:12.699275643 +0300
@@ -303,6 +303,10 @@
zend_objects_store_free_object_storage(&EG(objects_store) TSRMLS_CC);
+ /* CVE-2026-109618: may be used by zend_error() before init_executor() */
+ EG(error_handling) = EH_NORMAL;
+ EG(exception) = NULL;
+
/* Destroy all op arrays */
if (EG(full_tables_cleanup)) {
zend_hash_apply(EG(function_table), (apply_func_t) clean_non_persistent_function_full TSRMLS_CC);
diff -ruN a/Zend/zend_llist.c b/Zend/zend_llist.c
--- a/Zend/zend_llist.c 2026-10-22 19:39:17.000000000 +0300
+++ b/Zend/zend_llist.c 2026-10-22 20:44:12.699275643 +0300
@@ -117,6 +117,8 @@
current = next;
}
+ l->head = NULL;
+ l->tail = NULL;
l->count = 0;
}
diff -ruN a/Zend/zend_operators.c b/Zend/zend_operators.c
--- a/Zend/zend_operators.c 2026-10-22 19:39:17.000000000 +0300
+++ b/Zend/zend_operators.c 2026-10-22 20:44:12.699275643 +0300
@@ -1252,7 +1252,7 @@
if (Z_STRLEN_P(result) < 0 || (int) (Z_STRLEN_P(op1) + Z_STRLEN_P(op2)) < 0) {
efree(Z_STRVAL_P(result));
ZVAL_EMPTY_STRING(result);
- zend_error(E_ERROR, "String size overflow");
+ zend_mm_safe_error_ex("String size overflow");
}
Z_STRVAL_P(result) = erealloc(Z_STRVAL_P(result), res_len+1);
@@ -1261,6 +1261,10 @@
Z_STRVAL_P(result)[res_len]=0;
Z_STRLEN_P(result) = res_len;
} else {
+ /* CVE-2026-109618: the sum must fit in int, including the trailing NUL */
+ if (UNEXPECTED(Z_STRLEN_P(op1) < 0 || Z_STRLEN_P(op2) < 0 || Z_STRLEN_P(op1) > INT_MAX - 1 - Z_STRLEN_P(op2))) {
+ zend_mm_safe_error_ex("String size overflow");
+ }
Z_STRLEN_P(result) = Z_STRLEN_P(op1) + Z_STRLEN_P(op2);
Z_STRVAL_P(result) = (char *) emalloc(Z_STRLEN_P(result) + 1);
memcpy(Z_STRVAL_P(result), Z_STRVAL_P(op1), Z_STRLEN_P(op1));
diff -ruN a/Zend/zend_strtod.c b/Zend/zend_strtod.c
--- a/Zend/zend_strtod.c 2026-10-22 19:39:17.000000000 +0300
+++ b/Zend/zend_strtod.c 2026-10-22 20:44:12.699275643 +0300
@@ -473,7 +473,7 @@
Bigint *rv;
if (k > Kmax) {
- zend_error(E_ERROR, "Balloc() allocation exceeds list boundary");
+ zend_mm_safe_error_ex("Balloc() allocation exceeds list boundary");
}
_THREAD_PRIVATE_MUTEX_LOCK(dtoa_mutex);
@@ -484,7 +484,7 @@
rv = (Bigint *)MALLOC(sizeof(Bigint) + (x-1)*sizeof(Long));
if (!rv) {
_THREAD_PRIVATE_MUTEX_UNLOCK(dtoa_mutex);
- zend_error(E_ERROR, "Balloc() failed to allocate memory");
+ zend_mm_safe_error_ex("Balloc() failed to allocate memory");
}
rv->k = k;
rv->maxwds = x;