[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: 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.

« previous php.bugs (#252971) next »