[php-src] Issue #24257: Potential incorrect sign conversion of uint32_t to zend_long in unpack() on 32-bit builds (ext/standard/pack.c)
| From: | ushevchenko | Date: | Sun, 11 Oct 2026 09:13:14 +0000 |
| Subject: | [php-src] Issue #24257: Potential incorrect sign conversion of uint32_t to zend_long in unpack() on 32-bit builds (ext/standard/pack.c) | ||
| Groups: | php.bugs | ||
| Request: | Send a blank email to php-bugs+get-252971@lists.php.net to get a copy of this message | ||
Issue: https://github.com/php/php-src/issues/24257
Author: ushevchenko
### Description
In
ext/standard/pack.c at lines 1076–1084, unpack() assigns a
uint32_t value to a zend_long variable for the l,
L, N and V formats:
<PERMALINK 8.3.31, строки 1076–1084>
The same conversion is present in master (the byte swap is done on x first, then
v = x; is assigned):
<PERMALINK master, строки 1243–1264>
On 32-bit PHP builds, values greater than INT32_MAX may become negative due to the
implementation-defined conversion to a signed integer type. As a result, unpack() with
the L, N and V formats may return negative values for
unsigned 32-bit input. This does not affect 64-bit builds, where zend_long is
int64_t and can represent the entire range of uint32_t.
Unsigned 32-bit values above INT32_MAX are valid input for these formats (for example,
network byte order fields). The signed variant in the same function uses an explicit cast (v =
(int32_t) x; for l), while the unsigned path relies on an implicit conversion,
so the intended result is not visible in the code.
Note that the implicit conversion in unpack() was discussed in #6908 ("simplify
unpack logic"): the result of converting an out-of-range unsigned value to a signed type is
implementation-defined (C11 6.3.1.3 §3) and mainstream compilers implement it as two's
complement. This issue does not propose to change the resulting values, only to make the conversion
explicit.
This issue was identified by static analysis and has not been reproduced at runtime. Reproducing it
requires a 32-bit PHP build and unpack() input with a 32-bit value greater than
INT32_MAX.
Related: #24223 (same class of finding in ext/posix/posix.c).
## Possible solution
Add an explicit cast to zend_long. In PHP-8.3:
```c
v = (zend_long) php_pack_reverse_int32(x);
...
v = (zend_long) x;
```
In master the byte swap is done on x before the assignment, so only the final
assignment changes:
```c
v = (zend_long) x;
```
The returned values do not change on any platform.
Found by Linux Verification Center (portal.linuxtesting.ru) with SVACE.
Author: Uliya Shevchenko <u.shevchenko@fobos-nt.ru>.
### PHP Version
```plain
8.3.31
```
### Operating System
Not OS-specific; relevant to 32-bit builds.