[php-src] Issue #24492: sprintf()/pack()/bin2hex() _exit(137) at hard memory limit where 8.5.11 raised a catchable OOM (incomplete fallible-alloc
conversion, 8.5.12/8.4.27)
| From: | VijhnAustin | Date: | Wed, 21 Oct 2026 18:08:52 +0000 |
| Subject: | [php-src] Issue #24492: sprintf()/pack()/bin2hex() _exit(137) at hard memory limit where 8.5.11 raised a catchable OOM (incomplete fallible-alloc conversion, 8.5.12/8.4.27) |
||
| Groups: | php.bugs | ||
| Request: | Send a blank email to php-bugs+get-253059@lists.php.net to get a copy of this message | ||
Issue: https://github.com/php/php-src/issues/24492
Author: VijhnAustin
### Description
Since commit
31c0ea7d ("Avoid state corruption via bailout",
CVE-2026-109618/109619), a **non-fallible** allocation that would cross the new hard memory limit
(default 200% of memory_limit) terminates the process via _exit(137) in
zend_mm_hard_limit_error() (Zend/zend_alloc.c). Most allocation sites were
moved to the new fallible API, but several core functions still allocate non-fallibly from a small,
caller-supplied size, so they kill the whole process instead of raising the catchable "Allowed
memory size exhausted" fatal that 8.5.11 produced for the same input.
The following code:
```php
<?php
sprintf("%400000000s", "");
```
Run with: php -n -d memory_limit=128M repro.php
Resulted in this output (8.5.12):
```
Hard memory limit of 268435456 bytes exhausted (tried to allocate 505413632 bytes) in Command line
code on line 1
```
Exit code **137** — process terminated; the allocation is never actually performed (the hard-limit
check rejects first).
But I expected this output instead (the 8.5.11 behavior):
```
Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 503316512 bytes) in
Command line code on line 1
```
Exit code **255** — catchable fatal, the process/worker survives.
The same process kill occurs from other small integer parameters —
pack("x300000000"), number_format(1.0, 100000000),
bcadd("1","1",100000000) — and from large in-heap inputs to
bin2hex / base64_encode / urlencode / var_export
/ convert_uuencode / utf8_encode / quoted_printable_encode. A
crafted phar:// manifest length triggers it from a ~100-byte file.
Sites still using the non-fallible allocator in 8.5.12:
- smart_string_alloc() in PAD_CHAR — main/spprintf.c
- zend_string_alloc(outputsize, 0) — ext/standard/pack.c:431
- zend_string_safe_alloc(..., 0) in php_bin2hex() /
php_utf8_encode() — ext/standard/string.c
Mechanism: zend_mm_check_limit() calls zend_mm_hard_limit_error() →
_exit(137) when !heap->fallible and the allocation would cross
hard_limit. Routing these sites through the fallible API (femalloc /
zend_string_finit / smart_str_ferealloc) would keep the OOM catchable,
consistent with the rest of the patch.
Reproduces identically on 8.4.27 (8.4.26 behaves like 8.5.11). The path is in
Zend/zend_alloc.c, so it is platform-independent; tested here on official Windows x64
NTS builds.
### PHP Version
```plain
PHP 8.5.12 (cli) (built: Oct 20 2026 04:19:56) (NTS Visual C++ 2022 x64)
Copyright (c) The PHP Group
Built by The PHP Group
Zend Engine v4.5.12, Copyright (c) Zend Technologies
```
### Operating System
Windows 11 (code path is OS-independent — Zend/zend_alloc.c)