CVE-2026-109618: How a PHP Out-of-Memory Jump Hands the Cleanup Routine a Half-Built Object

The other half of PHP2Shell. A crafted request exhausts memory while PHP is still building an internal object, PHP's longjmp jumps over the code that would finish it, and request shutdown then runs over the result, all before your application code ever executes.

On 21 October 2026 the PHP project released an emergency fix for two critical, unauthenticated remote-code-execution bugs in its core engine, disclosed together under the nickname PHP2Shell. This article covers the first of the pair, CVE-2026-109618, “RCE via OOM corruption.” Its sibling, CVE-2026-109619, “RCE via stale global state,” has its own write-up, and the two share a scope and a fix.

Like its sibling it is rated CVSS v4.0 9.2 (Critical), and it was announced by Jakub Zelenka for the PHP security team on the oss-security mailing list at 16:41 CEST on 21 October. The full technical write-up is GitHub Security Advisory GHSA-w8hw-w9fx-j79w.

The one-sentence version

From the PHP security team’s announcement:

“PHP uses longjmp() to abort request handling on out-of-memory (OOM) errors. An allocation failure can interrupt initialization while incomplete request state is already reachable. Request shutdown may then access this invalid state, potentially allowing remote code execution.” (Jakub Zelenka, PHP security team, oss-security, 21 Oct 2026)

Where CVE-2026-109619 is about a pointer that lives too long and crosses into the next request, CVE-2026-109618 stays inside a single request. Running out of memory makes PHP jump out of a half-finished operation, and it then cleans up as if that operation had completed.

The mechanism: a jump over a half-built object

To keep its own memory use bounded, PHP enforces a memory_limit. When an allocation would cross it, or when the underlying system allocator genuinely fails, PHP has historically bailed out of the current operation with the C primitive longjmp(), a non-local jump that unwinds straight back to a saved recovery point.

The problem is when that jump can fire. PHP builds internal data structures step by step. It will, for instance, update a container’s bookkeeping (counters, metadata) and then allocate the storage that the bookkeeping describes. If the allocation in the middle of that sequence fails, longjmp() leaps out after the metadata was updated but before the object was finished. The structure now claims to hold something that was never actually initialized.

PHP’s per-request shutdown then runs, and in some servers it runs unconditionally, even when request start-up failed. Shutdown walks the structure, trusts the metadata, and operates on memory that is not what the metadata says it is. That is the “OOM corruption”: a cleanup routine acting on a half-built object.

Single-request out-of-memory path: a longjmp abandons a half-built object and request shutdown then trusts its metadata and acts on uninitialized memory.CVE-2026-109618: an out-of-memory jump over a half-built object1Request start-upPHP parses the request body before anyapplication code runs.2Allocation failsA crafted body exhausts the memory limitmidway through building an internalstructure.3longjmp jumps outControl leaps back to a recovery point,past the code that would finish thestructure.4Shutdown runs anywayCleanup walks the structure, trusts itsmetadata, and acts on memory that wasnever initialized.longjmpskips thefinish stepthe half-built objectmetadata: count = 2slot 0 initializedslot 1 NOT initialized(allocation never ran)metadata says “two items”;only one was ever built.Because it all happens during start-up, authentication and application-level filtering never get the chance to run.initializeduninitialized / freed
A crafted body exhausts memory while an internal structure is partially initialized, longjmp abandons the operation, and request shutdown then trusts the metadata and acts on memory that was never initialized.

The weakness classes the advisory assigns capture this precisely: CWE-755 (improper handling of exceptional conditions), CWE-824 (access of an uninitialized pointer), and CWE-1325 and CWE-1419 (improperly controlled sequential allocation and incorrect initialization of a resource).

Why it fires before your code runs

The reason this is rated so highly is timing. PHP parses the incoming request body, including multipart/form-data and URL-encoded form fields, during request start-up, before a single line of your application runs. An attacker who can make that early parsing exhaust memory triggers the whole sequence pre-authentication.

The advisory catalogs several ways a request that looks within limits can still blow past memory_limit, because PHP’s in-memory representation of parsed input can be far larger than the bytes on the wire. The upshot, in the words of Datadog’s independent reproduction, which triggered the corruption on PHP 8.5.11 with a single unauthenticated POST, is that “nothing in the target script can impact or defend against the exploit (authentication, filtering…).”

Which servers are affected

The PHP team is clear that this is engine-level, but the severity depends on the server module (the SAPI) that connects PHP to the web server, because each one treats a failed start-up differently.

  • Apache mod_php is the highest risk and the confirmed RCE. It “unconditionally invokes the request shutdown procedure, even when startup fails.” Automatic body parsing during start-up can exhaust the limit, and shutdown then processes the invalid state before any application code runs. The advisory reports RCE over HTTP with default limits, including multi-worker prefork setups.
  • PHP-FPM is partly susceptible. FPM exits the child when start-up fails, which prevents consumption of the invalid state in that window. An out-of-memory error after a successful start-up may still reach the issue, but no complete FPM exploit was demonstrated for this CVE.
  • FrankenPHP and LSAPI are likely but unvalidated. Source analysis suggests FrankenPHP is susceptible, since it calls shutdown unconditionally, and LSAPI is modeled on FPM.

The PHP release summary names the full at-risk set as “all PHP installations serving web requests, including PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP and LiteSpeed,” and repeats that the risk is highest for mod_php.

How far it reaches

CVE-2026-109618 shares its scope with its sibling, which is to say most of the web.

Scope of PHP2Shell: PHP runs roughly 70 percent of the web, every release since 5.4.0 is affected, and millions of hosts are internet-facing.How far it reaches~70%of the webPHP's share of sites whose server-sidelanguage is known (W3Techs: 69.3%).every releaseaffected since PHP 5.4.022.6Mexposed instances (Cortex Xpanse)1.3-1.9Minternet-facing PHP hostsCVSS 9.2critical, unauthenticated RCE
PHP runs roughly 70 percent of the web, every release since 5.4.0 is affected, and millions of hosts are internet-facing.
  • Reach. PHP powers around 70 percent of all websites (Wiz), or 69.3 percent of sites whose server-side language is known (W3Techs, via Tenable).
  • Version range. Fixed in 8.2.35, 8.3.36, 8.4.27 and 8.5.12, but affected code reaches back to PHP 5.4.0 according to Calif.io.
  • Exposure. Palo Alto Cortex Xpanse counted “over 22.6 million exposed instances potentially vulnerable” to the pair (Unit 42). Host-level estimates range from 1.3 million (Silas Cutler) to 1.9 million (Shodan, Henk Poley), via Tenable.

What makes it more or less dangerous

What raises the risk:

  • mod_php and default limits. The demonstrated exploit runs before application code, and PHP’s default memory limits are enough. As Datadog notes, “PHP’s default memory limits are vulnerable.”
  • Pre-disclosure exploitation. Amazon Threat Intelligence logged an attempt on 17 October 2026, four days before the patch, and reports broad exploitation since (via Tenable). watchTowr says it “observed exploitation of both CVE-2026-109618 and CVE-2026-109619 in the wild.” Unit 42 noted that at least one European government took its site offline as a precaution.
  • AI lowers the cost of a working exploit. Reliable exploitation requires sizing a request to fail at exactly the right moment and defeating ASLR, and researchers describe current models as collapsing the per-target tuning effort that used to make this impractical.

What lowers the risk:

  • Reliability is genuinely hard. An attacker has to force the allocation to fail at a precise point and locate valid memory addresses under ASLR. Rapid7 found public proof-of-concept code to be target-specific, with “none” that “appears broadly reliable,” and PHP “did not identify any functional exploits at the time of release.” This is a window to patch, not an all-clear.
  • FPM’s fail-fast behavior. Exiting the child on start-up failure closes the most direct, application-independent path.

The fix, and the one config change that is not a fix

The patch removes longjmp() from out-of-memory handling and replaces it with a two-tier scheme.

Before and after the patch: a deferred soft out-of-memory limit lets operations finish and a higher hard limit exits immediately instead of a longjmp into cleanup.What the patch changes about out-of-memory handlingbefore · vulnerableAn allocation failure triggers animmediate longjmp out of the operation.State is left half-built, and requestshutdown then runs over it.after · patchedSoft limit: the error waits for the nextsafe point, so the operation finishes first.Hard limit: the worker exits at once with_exit(137), never cleaning up bad state.The same release also resets request-owned global state before anything can reuse it, closing the other half of PHP2Shell.There is no complete configuration workaround. Updating is the only reliable fix.
The patch replaces the longjmp with a deferred soft limit that lets an in-progress operation finish, and a higher hard limit that exits the worker immediately instead of running cleanup over invalid state.

A soft limit defers the out-of-memory error until PHP reaches a safe point, letting an in-progress operation finish initializing its data before the error is raised. A hard limit, at a higher threshold, terminates the worker immediately with _exit(137) instead of running normal cleanup over possibly invalid state.

Datadog’s analysis adds a sober caveat: the underlying pattern of “updating metadata before allocating the memory it describes” still exists in places, so “we expect more of these bugs to surface.” The engine-level fix closes the known cases. It does not refactor the whole pattern away.

Update to a patched release:

Branch Fixed version
PHP 8.5.x 8.5.12
PHP 8.4.x 8.4.27
PHP 8.3.x 8.3.36
PHP 8.2.x 8.2.35
PHP 7.4 to 8.1 No official release; the security team published minimal source backports
PHP 5.4 to 7.3 End of life, so upgrade to a supported branch

Restart every worker after updating, and verify the version serving web requests, not the CLI binary.

The widely shared stop-gap for this specific CVE is memory_limit=-1, applied so that applications cannot override it, for example through php_admin_value in the FPM pool or Apache config. The advisory is candid about why that is only partial: “This removes the controllable PHP memory limit failure, but an underlying allocation failure can still reach the vulnerable path.” The release notes are blunter still: “There are no complete mitigations, updating is the only safe choice.” One more sharp edge from the advisory: do not use RLIMIT_AS or RLIMIT_DATA to bound memory, since they make mmap() fail and can trigger the vulnerable path.

What to look for

Datadog recommends correlating signals at three layers: large or unusual form submissions, memory-exhaustion errors during request initialization, and unexpected shell processes spawned by web workers. A successful exploit “doesn’t necessarily crash the worker or return an error,” and one refined demonstration returned HTTP 200 while the same worker kept serving, so a clean access log is not evidence of safety. Controls at a reverse proxy in front of PHP, capping decoded request size, field and part counts, and nesting depth, can block malicious requests before they reach the vulnerable parser, though PHP stresses these reduce exposure rather than close the hole.

In short

CVE-2026-109618 is what happens when an error-handling shortcut, a longjmp() straight out of a failed allocation, meets a cleanup routine that trusts half-finished bookkeeping, all during the request start-up window that runs before your code. The scale is the story: a decade of PHP releases, roughly 70 percent of the web, and millions of exposed hosts. The response is the same two sentences as for its sibling. Patch to a fixed version, restart your workers, and confirm the version that serves your traffic.


Primary sources

Companion article: CVE-2026-109619, RCE via stale global state.

Share: X (Twitter) Facebook LinkedIn