[php-src] Issue #24563: Allocation-failure call sites still on zend_error_noreturn(E_ERROR) on master (zend_strtod.c "Balloc() failed",
zend_multiply.h) — the EOL-7.4 minimal mitigation hardened these, master did not

From: Date: Thu, 22 Oct 2026 17:22:40 +0000
Subject: [php-src] Issue #24563: Allocation-failure call sites still on zend_error_noreturn(E_ERROR) on master (zend_strtod.c "Balloc() failed",
zend_multiply.h) — the EOL-7.4 minimal mitigation hardened these, master did not
Groups: php.bugs 
Request: Send a blank email to php-bugs+get-253107@lists.php.net to get a copy of this message
Issue: https://github.com/php/php-src/issues/24563 Author: srayuth089 ## Context While studying the fixes for GHSA-w8hw-w9fx-j79w / GHSA-6wmf-4rv5-p3rp (thank you for the delayed-OOM machinery in 3584a98bda — the design is excellent), we noticed a consistency gap between the two fix branches: - The EOL-7.4 minimal mitigation **712e60f9ed** converted several allocation-failure call sites to the fail-fast zend_mm_safe_error_ex() path. - The full 8.x fix and current **master** kept several such call sites on zend_error_noreturn(E_ERROR, ...), i.e. they still run the error machinery and bail out via longjmp from the failing context — the exact pattern the advisory describes as dangerous when the allocator is in a failing state. This is an **informational hardening-consistency observation, not a vulnerability report** — see the empirical section: we could not reach any of these paths from PHP userland under the conditions we tested. ## Call sites in question (master, checked 2026-10-22) | Location | Code | Note | |---|---|---| | Zend/zend_strtod.c ~590 / ~603 | zend_error_noreturn(E_ERROR, "Balloc() failed to allocate memory") when MALLOC() (raw malloc, #define MALLOC malloc at ~237) returns NULL | dtoa allocates Bigints outside Zend MM entirely (also unaccounted by memory_limit) | | Zend/zend_multiply.h ~343 / ~355 | zend_error_noreturn(E_ERROR, "Possible integer overflow ...") | size-arithmetic checks; 7.4 converted both | | ext/sodium/libsodium.c ~42 | zend_error_noreturn(E_ERROR, "Memory allocation too large ...") | untouched in both branches | ## Empirical work (why we rate this informational, not exploitable) We tried hard to reach the Balloc() path from userland before writing this up. PHP 8.4.27, arm64 (docker), instrumentation at three levels — all evidence below is reproducible and we are happy to share the tooling (a ~30-line LD_PRELOAD malloc interposer, RLIMIT_AS sweep harness, phase-marker snippets): 1. **Phase markers** (STDERR markers around string-build vs conversion): across an RLIMIT_AS sweep at **1 KB granularity**, every allocation failure occurred during string *construction* (Zend MM chunk mmap), before zend_strtod was ever entered. FAIL_IN_CONVERSION count: **0**. The transition was a cliff, not a window (67247 KB → fail in build, 67248 KB → full success). 2. **strace** (mmap/brk/mremap bracketed by the same markers): **0** memory-mapping syscalls during the conversion phase, both with comfortable VA and at the boundary. 3. **LD_PRELOAD malloc interposer** (libc-call level, which strace cannot see): conversion performs a **constant 11 malloc calls / 428 requested bytes** regardless of a **1024× input-length difference** (4,096 vs 4,194,304 mantissa digits). Controls: marker-only run = 0 allocations; a low-level fwrite() marker variant reproduced the same 11/428B figure, ruling out instrumentation overhead. Preliminary return-address attribution places one call near zend_strtod+0xee4. In other words: for the tested input domain, dtoa's memory need is bounded (digit truncation before Bigint construction; freelist/private pool absorbs the rest), so RLIMIT_AS pressure always surfaces in the string BUILD phase, where the new delayed-OOM machinery handles it gracefully. We did **not** find a way to make the libc allocator fail *inside* the conversion. ## Questions 1. Is leaving these on zend_error_noreturn intentional — i.e. does the delayed-OOM layer on master also cover a libc-malloc failure deep inside dtoa, making the 7.4-style conversion unnecessary there? 2. If it is an oversight, would a small PR converting the four call sites above to the fail-fast pattern (mirroring 712e60f9ed) be welcome? Happy to send it. 3. Separately: is the fact that dtoa allocates via raw malloc() (outside memory_limit accounting) considered acceptable? Our measurements suggest per-conversion usage is tiny and bounded, so this may be fine as-is — asking for completeness. ## Reproduction context The interest in this area came from lab work on the php2shell chains (public PoC and a public Metasploit module PR exist for CVE-2026-109618/109619). We validated the patched build behavior end-to-end: on 8.4.27 the public chain dies at the heap-leak stage with zero worker crashes, consistent with the fix cutting off stale-state reuse. No new exploitation is reported or implied by this issue.

« previous php.bugs (#253107) next »