maborak.com~/blog/cve-2026-109618-php-oomsoftware & infrastructure engineering
Back to all articles

CVE-2026-109618: PHP memory exhaustion and request cleanup

How PHP's OOM bailout corrupts request state, why mod_php and FPM differ, and how to verify a patched deployment.

By the Maborak engineering team · · 6 min read

CVE-2026-109618 concerns what PHP does after a memory allocation fails. A request can fail before application code gets an opportunity to reject it. The operational priority is to identify the PHP runtime handling HTTP traffic, update that runtime and replace the workers still using the old build.

This analysis was checked on 29 October 2026. The vulnerability details below come from PHP’s advisory; the verification procedure is our operational recommendation. We have not independently reproduced remote code execution.

Where the failure occurs

PHP’s affected OOM handling uses longjmp() to escape an allocation failure. If request state has become reachable but is not fully initialized, the escape can bypass the remaining initialization. Shutdown may then use the incomplete state. Multipart parsing provides several ways to exhaust memory, including allocation overhead that is larger than the incoming body. The fix replaces this OOM bailout with a deferred soft failure at a VM safe point and a hard _exit() at a higher threshold. PHP technical advisory.

The underlying engineering issue is a broken lifetime invariant. A cleanup routine needs to know whether a resource was never created, is partially initialized, or is ready for use. Those states cannot safely be represented by a single optimistic “ready” flag.

Consider this simplified example. It illustrates failure ordering; it is not PHP source or an exploit:

/* Unsafe ordering if allocation can escape this function. */
state->ready = true;
state->buffer = allocate_buffer();
initialize_buffer(state->buffer);

/* Cleanup assumes ready implies a fully initialized buffer. */
if (state->ready) {
    release_buffer(state->buffer);
}

Returning an error from allocate_buffer() would let its caller handle failure. A nonlocal exit changes that contract: execution can resume in an outer handler without running the caller’s next statement. A defensive implementation must make every observable intermediate state safe, or terminate without entering cleanup that assumes successful initialization. This is why a patch to the runtime matters even when the application already catches exceptions correctly.

Why the serving interface matters

With mod_php, request shutdown still runs after startup failure. The documented attack can therefore reach corrupted state before the application starts. FPM terminates its child on startup failure, but application-stage OOM remains relevant; PHP’s advisory does not claim a complete demonstrated FPM exploit for this CVE. A frontend change alone is not a complete fix. PHP technical advisory.

Red Hat assesses its typical FPM deployments differently from mod_php because of these lifecycle differences. Its rating is Important, with CVSS v3 8.1. That assessment should not be generalized to every PHP application or packaging vendor. Red Hat CVE record.

For an inventory, record the serving interface alongside the version. “PHP installed” is too broad; “PHP-FPM pool checkout, reachable through nginx server payments, package build X” is an actionable finding. An unused CLI binary and an exposed Apache module need different remediation work.

Fixed releases and vendor packages

The first fixed upstream releases are:

Branch Fixed release
PHP 8.2 8.2.35
PHP 8.3 8.3.36
PHP 8.4 8.4.27
PHP 8.5 8.5.12

These releases also address CVE-2026-109619. PHP release announcement.

A distribution may backport a security fix while retaining an older upstream version string. Red Hat explicitly documents this possibility. Match the installed package release to the vendor’s advisory instead of closing or reopening the incident from PHP_VERSION alone. Red Hat package guidance.

For each deployment, retain the package identifier or immutable image digest, the advisory that establishes its fixed status, and the time the new worker generation began serving. Those three facts are more useful than a screenshot of a successful package-manager command.

Verify the HTTP runtime, not just the shell

Start with local inventory:

php -v
php --ini

Those commands inspect the CLI environment. PHP can load different configuration files for different server interfaces, and additional INI files can be scanned from configured directories. The web runtime must be checked separately. PHP configuration file documentation.

For a controlled diagnostic, run the following through an existing access-restricted operational route in the relevant application. Do not leave a public diagnostic endpoint behind. It prints selected values rather than the environment, request headers or credentials:

<?php
header('Content-Type: application/json');
header('Cache-Control: no-store');
echo json_encode([
    'version' => PHP_VERSION,
    'sapi' => PHP_SAPI,
    'pid' => getmypid(),
    'memory_limit' => ini_get('memory_limit'),
    'post_max_size' => ini_get('post_max_size'),
    'upload_max_filesize' => ini_get('upload_max_filesize'),
], JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR);

A single response proves only which process answered that request. Repeat the check against each backend or pool and compare it with process inventory. In a rolling deployment, ensure the load balancer has drained the old generation. An updated image tag in a manifest does not establish that every running replica uses the new image.

Our acceptance procedure is to check a normal page, an authenticated operation, a representative upload and an application error response after replacement. Then compare worker restarts, error rates and memory consumption with the pre-change baseline. This validates the deployment; it does not attempt to prove safety by firing an exploit at production.

Memory limits require careful interpretation

PHP documents memory_limit as a per-script allocation limit. post_max_size limits POST data, and upload_max_filesize constrains an individual uploaded file. These settings measure different things; none is a direct measurement of total resident memory across a pool. PHP core configuration.

The advisory lists memory_limit=-1 as an incomplete temporary mitigation. It removes the predictable PHP limit, but underlying allocation failures can still reach the vulnerable path. It also warns against replacing it with RLIMIT_AS or RLIMIT_DATA. Updating remains the complete mitigation. PHP technical advisory.

We would treat any temporary limit change as a capacity change: record worker concurrency, available memory and the failure boundary before applying it. For example, if a pool can run 24 busy workers, evaluating only one worker’s memory use misses aggregate pressure. Account for the database, cache and other processes sharing the host. Do not copy an unlimited-memory setting into a fleet without a tested rollback and an expiry date.

What to retain during incident review

Collect request timestamps, affected route, backend identity, worker exit records and allocator-related errors. Keep the time zone explicit. An OOM message is evidence of a failed allocation, not proof of remote code execution. A quiet log is also insufficient to establish that exploitation was impossible.

If investigation finds unexpected executable files, unexplained child processes or unauthorized changes, preserve evidence and follow the incident-response process before treating the patch as closure. Runtime replacement repairs the vulnerable code path; it does not explain or reverse activity that occurred beforehand.

The second PHP2Shell issue has a different lifetime boundary: state can survive into a later request in the same worker. Continue with CVE-2026-109619 and stale worker state. The later session advisory is covered separately in CVE-2026-115560.

Back to all articles

Technologies we work with

  • Argo CD
  • Kubernetes
  • Docker
  • Podman
  • Ansible
  • Jenkins
  • Chef
  • Proxmox VE
  • Google Cloud
  • Linux
  • Red Hat
  • CentOS
  • NGINX
  • Apache
  • Traefik
  • Prometheus
  • Grafana
  • PostgreSQL
  • MySQL
  • MariaDB
  • Redis
  • etcd
  • Qdrant
  • Elasticsearch
  • Logstash
  • Kibana
  • PagerDuty
  • Go
  • Python
  • FastAPI
  • PHP
  • TypeScript
  • React