Skip to content
PaxDynamics.Let’s talk
English
Latest post:PHP after the patch
Back to Blog

PHP after the patch

A guide to the tested PHP session chain: what ran in local labs, which application conditions mattered, what failed, and what remains open.

The October 21 PHP security releases closed two PHP2Shell paths. We asked a different question: could a session record still reach the older destructor re-entry bug on those release builds?

On PHP 8.4.27 and 8.5.12, the answer in our local labs was yes. A serializer mismatch delivered the record to PHP's own decoder; delayed cleanup invoked a destructor; nested parsing reused freed bookkeeping. A separate stock-allocator test reached a fixed command. The path required enabled upload progress, a runtime serializer switch, a suitable loaded destructor, memory placement, and an address source. Those conditions define the result.

This series follows that chain from the engine to application code, then follows the experiments that failed. The October 21 release notice is the baseline, not evidence that a default PHP installation exposes this path.

The underlying bug and composition were already public. Dinosn published the destructor re-entry bug; Calif published related unserializer research; and ACD421 published the four-stage composition. Our contribution is a measured port to the named release builds, controls that stop the tested chain, application profiles, and documented failures. The prior-art correction records why we narrowed the original novelty claim.

INTERACTIVE RESEARCH / 01

One record. Several boundaries.

Follow a stored record across a format boundary, into parser cleanup, and out to the limits of the application results.

SELECT AN OBJECT TO INSPECT GEOMETRY + TIMING SCHEMATIC
Before application code

The upload-progress hook writes a session record using the configured serializer. Application routing has not started.

Recorded evidence ↗Read this chapter ↗

Reduced motion: select steps to change the diagram. Symbolic objects; observed results keep their recorded conditions.

Transcript and recorded profiles
  1. Before application code. The upload-progress hook writes a session record using the configured serializer. Application routing has not started. Evidence · Article
  2. The record stays; the reader changes. A runtime switch selects the legacy php reader. With the mismatch present, part of the same record can reach the unserializer. Evidence · Article
  3. Objects bring cleanup work. The parser tracks created values and delayed cleanup. A suitable loaded destructor is an additional condition. Evidence · Article
  4. Lifetime ends before callbacks finish. The reviewed cleanup releases bookkeeping before the destructor walk finishes. Nested parsing can follow the stale context. Evidence · Article
  5. Two different experiments. ASAN establishes an invalid read. A separate stock-allocator profile needed gadget, placement, and address conditions to reach a fixed command. Evidence · Article
  6. The application sets the boundary. Drupal required configuration; Roundcube required a lab plugin. Joomla and our WordPress candidate stopped earlier. FPM and native address sources remain open here. Evidence · Article
  • Handler mismatch: Tested delivery path. Configured producer and runtime legacy reader; enabled upload progress and suitable loaded destructor. Evidence
  • No runtime switch: Delivery stopped. Removing the runtime handler switch stopped the tested delivery. Other PHP paths are outside this control. Evidence

Read the research

  1. →The destructor path that survived the patch follows the engine path from the session decoder to the memory error and the local fixed-command result.
  2. →Following the chain into application code tests the conditions in Drupal, Joomla, Roundcube, and WordPress. It separates a completed lab profile from a loaded gadget or a blocked path.
  3. →What the failed tests taught us records the version, Redis, FPM, architecture, and fuzzing limits.

The earlier session-decoder technical record preserves the detailed test sequence, payload shapes, and controls. Start with the three-part series for the current account. One byte past the buffer covers a separate SJIS-mac mbstring finding; it is outside this session chain.

Results and reproduction boundaries

ObservationPublic evidenceBoundary
An instrumented run read freed memory during var_destroy() cleanupPatch-gap record and minimal verifierAn ASAN report establishes the memory error, not command execution.
The tested session chain reached a fixed command on 8.4.27 and 8.5.12Session-chain verifier and loopback labThe result depends on the named configuration, gadget, memory placement, and address source.
Application code changed which part of the chain was reachableApplication results, Drupal lab, and Roundcube labDrupal was configuration-gated; Roundcube was plugin-assisted; Joomla stopped at the memory error; our WordPress candidate path was blocked.
Several proposed routes stopped before a complete chainNegative-results record, version verifier, and fuzz harnessThe historical fuzz count has no saved per-run transcript; our FPM and native x86-64 address sources remain incomplete.

The php2shell-hunt repository contains the public research files. Links in the table point to the reviewed 56f0331 commit so the evidence stays identifiable. All described command tests ran in local containers with the fixed command id>/tmp/vd 2>&1.

Upstream status on November 2, 2026

Open work

The next experiments address the missing parts of the record. Each row states what must be preserved for an independent review. Tests run in owned, loopback-only environments.

Next stepEvidence needed to close it
Recheck the var_destroy() path after PHP publishes a release with the upstream changesRecord the exact PHP build and source revision; rerun the minimal memory-error test, the session profile, and their controls. Publish positive and negative results.
Preserve a fresh fuzzing recordRun a bounded campaign with versioned inputs, command lines, ASAN output, and a run manifest. Keep the historical 4.1 million-execution figure labelled as a summary without a preserved transcript.
Reproduce the application profiles from clean lab setupRecord container digests, configuration, fixed-command result, and negative controls for Drupal and Roundcube. Seek an independent rerun when possible.
Resolve the FPM and native x86-64 evidence gapsState the result of controlled local tests, including failures. Do not promote the lab-assisted FPM result or Rosetta run into a general native result.
Track the separate mbstring reportRecord vendor response, any fix and regression test, and the first released build that includes it. Keep its status separate from this session research.

Completion requires a dated result and a direct evidence link, including when a rerun fails.

The source review, controlled reproductions, and stated limits in this investigation are also part of our security research and application testing work. For an uncertain technical question, start with software R&D and prototyping.