[php-src] PR #23688: Fix ZEND_LONG_UINT_OVFL() dropping the negative half, and add the long range checks
| From: | marc-mabe | 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.