PHP2Shell and the November kernel updates

PHP's October emergency releases and Ubuntu's November kernel notices, and how a patch watch catches the running version — not the one in a lockfile.
7 min read
#php#linux#security#patches

PHP2Shell and the November kernel updates

Two patch cycles landed close together. On 21 October 2026 the PHP project shipped emergency releases for two critical bugs. In the first week of November, Ubuntu published kernel security notices for 20.04, 22.04, 24.04, and 26.04. Both are the kind of update a legacy app misses, because the version that matters is the one that is running.

This note is the public version of the check we do for clients. It is not an exploit write-up. The fix is the vendor's package, applied where the process actually starts, then proved.

What PHP shipped

PHP's 21 October advisory covers two vulnerabilities that can allow unauthenticated remote code execution:

  • CVE-2026-109618 (GHSA-w8hw-w9fx-j79w). An out-of-memory failure can interrupt PHP while request data is only partly initialised. Cleanup may then treat that invalid state as valid. PHP's advisory says the demonstrated case is Apache with mod_php, where the attack can run before application code.
  • CVE-2026-109619 (GHSA-6wmf-4rv5-p3rp). Request-owned state can survive after its memory is freed. A later request on the same worker may use that stale state.

PHP's advance notice rated both CVSS v4.0 9.2. The fixed releases are 8.5.12, 8.4.27, 8.3.36, and 8.2.35. PHP's own sentence is the one to keep: there is no complete mitigation, and updating is the only safe choice.

Who has to move:

  • Any PHP that serves web requests: PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP, LiteSpeed. PHP considers mod_php the highest risk.
  • PHP 8.1 and older get no release from the PHP project. The security team published minimal source backports for 7.4, 8.0, and 8.1 for people who build from source or have no long-term-support vendor. A distribution package is the other route. An unpatched 7.4 or 8.1 image is not "probably fine."
  • PHP 8.2 reaches end of life on 31 December 2026. 8.2.35 is the security fix, and it is also the last year of that branch.

If you cannot update the same day, PHP lists settings that reduce specific paths and do not make the install safe. The one they have repeated since is session.upload_progress.enabled=0, set in php.ini or the FPM pool, then a restart of every worker. On 8.5 they also named fatal_error_backtraces=0. For the out-of-memory bug they named memory_limit=-1 where the application cannot put a finite limit back. PHP says an underlying allocation failure can still hit the vulnerable path. We treat those lines as a weekend hold, and we write down that they are not the fix.

A later note on the same PHP news archive asks people to keep session.upload_progress.enabled=0 until the next bugfix release. PHP describes a further, unlikely path (they will publish it as CVE-2026-115560) that needs an unusual runtime switch of session.serialize_handler plus upload progress still enabled. Applications that never change that handler at runtime are outside that case. Keep the setting if you already applied it.

Windows builds were rebuilt again as 8.5.12-1, 8.4.27-1, 8.3.36-1, and 8.2.35-1 so the bundled Brotli decoder picks up CVE-2026-111497. The PHP version number did not change. The -1 suffix is the dependency rebuild. If you run PHP on Windows and cURL is allowed to decompress responses, that package is the one to install.

The same shape shows up outside PHP. A Rails app is the Ruby it boots plus the gems in the lockfile. An Elixir release is the OTP it was built with. A Go binary is the standard library and modules it was compiled against. Composer, Bundler, Mix, and go.sum do not tell you what the server is running until you read that process.

What Ubuntu shipped in November

Three notices are the ones we check first on client hosts:

  • USN-8959-1, 3 November 2026. Linux kernel on Ubuntu 26.04 LTS and 24.04 LTS, including the AWS, GCP, Oracle, and IBM flavours and the 24.04 hardware-enablement kernel. The generic 26.04 image in that notice is linux-image-7.0.0-42-generic (7.0.0-42.42). The 24.04 HWE line is the same upstream version with the ~24.04.1 suffix.
  • USN-8961-1, 3 November 2026. Linux kernel on Ubuntu 22.04 LTS and 20.04 LTS, including the cloud flavours (AWS, Azure, GCP, Oracle, GKE). The generic image named there is linux-image-5.15.0-202-generic (5.15.0-202.212).
  • USN-9003-1, 6 November 2026. The NVIDIA kernel packages on 26.04 LTS and 24.04 LTS (linux-nvidia-7.0, linux-nvidia-bos).

Each notice says the same operational thing: after the package update you reboot, or the running kernel is still the old one. Each also warns that an ABI change gives the kernel a new version number, so third-party modules have to be rebuilt and reinstalled. A standard upgrade does that for Ubuntu's own metapackages. A DKMS module, a vendor GPU driver, or an out-of-tree network driver does not update itself because apt finished.

The notices are large. They cover networking, filesystems, memory management, and a long list of drivers. One named example from USN-9003-1 is CVE-2023-20585: a local attacker with hypervisor access could hit an out-of-bounds case in AMD SEV-SNP guest memory. That is not the story for every VPS. The story for a typical legacy box is narrower and more reliable: the package is installed, uname -r is older, and the reboot never got a window.

Debian, RHEL, and the rebuilds (Alma, Rocky) published their own kernel updates the same month. We follow the vendor that actually signs the packages on that host. We do not paste an Ubuntu version onto a host that is not Ubuntu.

How this gets missed

The failures we see are boring.

  • The Dockerfile pins php:8.1 or an old php-fpm package. Composer is current. The interpreter is not. PHP2Shell is in the interpreter.
  • apt upgrade ran from a cron, the kernel image landed, and the box has an uptime of 400 days.
  • Deploy is Capistrano, a shell script, or a GitLab job that copies the app and never touches the OS.
  • Ansible provisions the host once. Nobody re-runs it. The repo and the server diverge.
  • The OS is past standard support, so the advisory's package does not exist in the repo you still have enabled.

A scanner that reads HTTP headers will not show you uname -r. A pull request that bumps composer.lock will not restart PHP-FPM.

What we do for a client

Two retainers cover this. They are separate because the repo is different.

Legacy Security Patches is one named Rails, PHP, Elixir, or Go codebase. The first week writes down the runtime that production starts, the lockfile, and the advisories that already apply. Each month after that is a security bump on a branch, the test suite you already have, and the version that deployed. If the vendor has no fix for that major — PHP 8.1 and older, unless you have a distribution backport — the note says so. We do not call a php.ini tweak a patch.

Legacy Pipeline Patches is the provision and deploy path: Ansible, shell, Capistrano, old CI, Dokku, or systemd, on the servers you already run. The month's work is the distro update inside that repo, a reboot in a window you agreed, and a check that the booted kernel matches the package. Out-of-tree modules get rebuilt when the notice says the ABI moved. A new Kubernetes cluster is the DevOps offer, not this one.

The watch list for a host is short enough to do every month:

  1. The language runtime and the lockfile, for the one app named in the SOW.
  2. The OS security repo: Ubuntu USN, Debian DSA, or the RHEL errata for that release.
  3. uname -r against the image the notice names. Uptime is part of the answer.
  4. The deploy job. If the next patch cannot be repeated from the repo, the patch is not finished.

We are a user on your repo and your host. We do not own the cloud account, and we do not turn a security bump into a feature release.

If this is the box you have been meaning to look at, the audit is a week: legacy app or legacy pipeline.