# From mono-implementation risk to spec: Lessons from CVE-2026-109618/109619 and a path to K/Redex semantics[Idea]

- Thread: https://externals.io/message/132917
- Original archive: https://news-web.php.net/php.internals/132917
- Started by Brian Nguyen on 2026-10-24T20:59:35+00:00
- Messages: 1
- Reply by email to internals@lists.php.net
- More for agents: search with https://externals.io/search.md?q={query} · RFCs at https://externals.io/rfc.md · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

---

## #132917 — Brian Nguyen — 2026-10-24T20:59:35+00:00

URL: https://externals.io/message/132917#132917

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:

1. 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.

2. 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
