[php-src] PR #23688: Fix ZEND_LONG_UINT_OVFL() dropping the negative half, and add the long range checks

From: Date: Tue, 15 Sep 2026 06:31:14 +0000
Subject: [php-src] PR #23688: Fix ZEND_LONG_UINT_OVFL() dropping the negative half, and add the long range checks
Groups: php.git-pulls 
Request: Send a blank email to git-pulls+get-38481@lists.php.net to get a copy of this message
Pull Request: https://github.com/php/php-src/pull/23688 Author: marc-mabe ZEND_LONG_UINT_OVFL() is named after an overflow but also reported a negative zend_long — and only when a zend_long is wider than an int. That is true on every 64bit platform and on none of the 32bit ones, where the macro expands to (0). So on 32bit the negative half was never checked at all. Every caller narrows the value into an unsigned int or a DWORD immediately afterwards, and the two in win32/codepage.c even spell out "must be between 0 and UINT_MAX" in the error they raise. None of them wanted the overflow on its own; they all silently lost the lower bound on 32bit. ## Naming The macro keeps its name and now does only what that name says. The family is completed in the shape of the existing signed one: | | overflow | underflow | both | |---|---|---|---| | unsigned int | ZEND_LONG_UINT_OVFL() | ZEND_LONG_UINT_UDFL() | ZEND_LONG_EXCEEDS_UINT() | | unsigned long | ZEND_LONG_ULONG_OVFL() | ZEND_LONG_ULONG_UDFL() | ZEND_LONG_EXCEEDS_ULONG() | | long | ZEND_LONG_LONG_OVFL() | ZEND_LONG_LONG_UDFL() | ZEND_LONG_EXCEEDS_LONG() | The _UDFL macros are deliberately *not* guarded by the width test: a zend_long is signed whatever its width, so it can underflow an unsigned type on every platform. That is precisely the half the old macro threw away. The second commit adds the long family because a long is not the width of a zend_long everywhere — LLP64 keeps it at 4 bytes while a zend_long is 8 — and zend_range_check.h only covered int and size_t. ## BC breaks **Internal (UPGRADING.INTERNALS)** — ZEND_LONG_UINT_OVFL() no longer reports a negative value. Out-of-tree callers relying on the old meaning lose their lower bound *silently*, which is why this is called out rather than just renamed. Use ZEND_LONG_EXCEEDS_UINT(). All eight in-tree call sites are updated. **Userland, 32bit only** — input that was wrongly accepted now raises: - Phar::buildFromIterator() — a negative SplFileInfo::getMTime() override was stored as if unsigned; now BadMethodCallException. 64bit already rejected it. - sapi_windows_cp_set() / sapi_windows_cp_conv() — a negative codepage was passed on as a DWORD; now ValueError. 64bit already rejected it. **Userland, all platforms** — mb_ereg_match() open-coded the same range test as the other regex functions but with > 0 && < UINT_MAX, so it disagreed with them for a limit of 0 or UINT_MAX. It uses the macro now, making the two paths agree. ## Fix, not a break onig_set_retry_limit_in_match_of_match_param() takes an unsigned long, not the unsigned int of the stack limit beside it. mbstring.regex_retry_limit was checked against UINT_MAX and therefore *dropped* above that, falling back to oniguruma's own default. It now honours the full range on LP64. ## Tests Three added. The phar and mbstring ones were each confirmed to fail with the fix reverted, so they genuinely pin the behaviour. The Windows one only exercises the changed path on a **32bit** Windows job — on 64bit it passed before and after. win32/codepage.c has not been compiled locally; CI is the first check of those three call sites.

« previous php.git-pulls (#38481) next »