Hi internals,
I want to raise a broader process question following the
emergency releases on Oct 21 for CVE-2026-109618 and
CVE-2026-109619.
I had noted privately back in 2020 that given the lack of a
formal spec and single-implementation status, I expected the
next critical vulnerability to be in the language runtime and
execution layer itself — not in ext/dom, ext/curl, ext/gd,
or HTTP handling — but in memory allocation and hashtable
management. The October disclosures matched that pattern,
which is why I'm writing now.
What happened:
-
CVE-2026-109618 (CVSS 9.2, "rawkey"): in Zend Engine's
HashTable implementation, _zend_hash_str_add_or_update_i
increments the entry count before allocating the new
key. If that allocation exceeds memory_limit, Zend calls
zend_bailout() via longjmp, abandoning the request with a
counted-but-uninitialized bucket. Request shutdown then
treats stale memory as a valid zend_string. Requires a
POST with a large field name (>1MB). -
CVE-2026-109619 (CVSS 9.2, "stale global state"): in
session.upload_progress, request-owned state is retained
after free and reused by a later request on the same
worker. Small body (~450 bytes).
Fixed in 8.2.35, 8.3.36, 8.4.27, 8.5.12. The advisories note
the underlying code goes back to PHP 5.4. Exploitation is
below the application layer — mod_php was confirmed
exploitable before any application code runs.
This is not a bug in an extension. It is a bug in ZendMM,
zend_hash, and request lifecycle — the core runtime.
Why this matters beyond the patch:
PHP today has no formal specification. As the KPHP work
(Filaretti et al., ECOOP'14) notes, Zend is the de-facto
spec. That makes this class of bug hard to catch:
-
No alternative implementation to disagree with Zend.
Python has CPython/PyPy/Cython, JavaScript has
V8/SpiderMonkey/JSC, .NET has CoreCLR/Mono. PHP had
HHVM and has PeachPie (.NET), but without a stable
bytecode/parser spec, compatibility diverged — HHVM
dropped PHP compat in 2019 to become Hack, PeachPie
needs workarounds for Laravel. We are back to one
reference implementation. -
No stable bytecode spec for independent tooling. The
vulnerable pattern — count-before-alloc + OOM bailout —
could have been modeled and fuzzed if the invariants of
HashTable and ZendMM were specified outside of C.
The broader ecosystem point:
.NET is a useful counter-example, not because "Microsoft
exists", but because it chose to keep alternative runtimes.
.NET Framework was proprietary and Windows-only. Mono (2001)
was the independent implementation that pushed it open.
Microsoft open-sourced .NET Core in 2014, it became truly
cross-platform (ASP.NET Core on Linux/macOS/Windows), and
today .NET intentionally ships both CoreCLR and Mono runtimes
to stress-test feature additions. The .NET Foundation model
is similar to what the PHP Foundation has been building
since 2021.
PHP's trajectory was the opposite: we lost the second
implementation. The cost is not that PHP becomes "irrelevant"
overnight — it powers a huge share of the web — but that
trust erosion makes teams choose Node/Python/Go for new
systems while keeping PHP for existing WordPress installs.
Long-lived runtime RCEs accelerate that.
Why I think a spec effort is more tractable now:
The CVE-2026-109618 lead was flagged by automated audit
tooling (Ryan Austin's agent flagged the count-before-alloc
in _zend_hash_str_add_or_update_i). AI-assisted auditing of
the Zend heap path is now practical.
Similarly, recent AI-assisted re-implementations of legacy
binary formats and apps from scratch (e.g., ArtCraft's work
cloning apps and data formats in Rust with no legacy code)
show that re-implementing a complex legacy format from a
reference implementation + test suite is more feasible today
than when KPHP was manually written in 2014.
We can leverage that to bootstrap PHP semantics.
Proposal for discussion, not an embargo:
I am not proposing freezing features or cosmetic deprecations.
I am proposing we use this incident to start:
A) Formalize Zend bytecode and parser as a living spec
extracted from php-src (8.4/8.5 baseline), including
OOM/bailout semantics.
B) Reference executable semantics in two formal frameworks:
- K Framework (building on KPHP)
- PLT Redex / Racket, to cross-check the model
Both should model HashTable invariants, ZendMM alloc
failure, and request lifecycle.
C) PyPy/Roslyn-style bootstrapping: a compiler from PHP to
Zend bytecode and to other targets (e.g., JS) to validate
semantics, plus self-hosting tests (compile the bootstrap
compiler with itself).
D) Conformance via differential testing: run phpt plus
WordPress, MediaWiki, Laravel, Symfony against any
alternative runtime, not just phpt.
If there is interest, I can draft a short RFC outline for
(A) + (B) as a first step, and talk to the PHP Foundation
(contact@thephp.foundation) about whether this is fundable
as Foundation work.
Thanks for reading this long mail,
Brian