[php-src] PR #24590: Harden core engine against integer overflows, allocator canary recovery, and SSA shift range unsoundness
| From: | mkrishna793 | Date: | Fri, 23 Oct 2026 06:46:49 +0000 |
| Subject: | [php-src] PR #24590: Harden core engine against integer overflows, allocator canary recovery, and SSA shift range unsoundness | ||
| Groups: | php.git-pulls | ||
| Request: | Send a blank email to git-pulls+get-39751@lists.php.net to get a copy of this message | ||
Pull Request: https://github.com/php/php-src/pull/24590
Author: mkrishna793
### Summary of Changes
This PR resolves several boundary overflows, undefined behaviors, and logic soundness issues across
the Zend Engine, Core Streams, and bundled extensions (
libmagic, timelib).
Key arithmetic invariants in these patches have been formally modeled and verified using the **Z3
Theorem Prover** and **Rocq / Coq** to guarantee zero false positives and zero regressions across
all 64-bit and 32-bit integer ranges.
---
### Key Issues & Fixes
#### 1. Streams: Integer Overflow in main/streams/memory.c
* **Issue:** In php_stream_memory_write() and php_stream_memory_seek(),
evaluating ms->fpos + count > data_len allowed 64-bit integer wraparound when
fpos was seeked near SIZE_MAX. If fpos + count wrapped to
$\le data\_len$, buffer reallocation was skipped while memcpy() proceeded to write to
ZSTR_VAL(ms->data) + ms->fpos, triggering an out-of-bounds heap write.
* **Fix:** Added hardware overflow checks via __builtin_add_overflow(). Any wraparound
immediately fails safely with (ssize_t) -1.
#### 2. Zend Optimizer: Soundness Flaw in shift_left_overflows()
(Zend/Optimizer/zend_inference.c)
* **Issue:** In zend_inference.c:762, shift_left_overflows(n, s) for
negative numbers checked safe_shift_left(n, s) > n. However, when negative inputs
shifted into ZEND_LONG_MIN (e.g., $n = \text{-7,137,322,560,452,408,418}$ shifted left
by 62 bits), (n << 62) wrapped around to $-2^{63}$. Because $ZEND\_LONG\_MIN <
n$, the check evaluated to false, falsely claiming that a multi-magnitude overflow did
not overflow. This allowed downstream passes (dfa_pass.c) to strip type/bounds guards
unsoundly.
* **Fix:** Replaced with an arithmetic round-trip shift check:
```c
if (UNEXPECTED((zend_ulong)s >= SIZEOF_ZEND_LONG * 8)) return true;
if (UNEXPECTED(s == SIZEOF_ZEND_LONG * 8 - 1)) return n != 0 && n != -1;
zend_long shifted = (zend_long)((zend_ulong)n << (zend_ulong)s);
return (shifted >> s) != n;