Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ PHP NEWS
. Use recommended TYPE, HELP order for OpenMetrics metadata.
(Marcel Hernandez)

- GD:
. Fixed imagetruecolortopalette() error message. (Marc Bennewitz)

- Hash:
. Fixed mhash_keygen_s2k() truncating a $length above INT_MAX instead of
throwing a ValueError. (Marc Bennewitz)
Expand All @@ -36,6 +39,11 @@ PHP NEWS
IntlCalendar::fromDateTime() fails after adopting a TimeZone.
(Ilia Alshanetsky)
. Added IntlNumberFormatter::createFromSkeleton() (GH-24165). (David Carlier)
. Fixed IntlDateFormatter parse functions truncating an $offset below
INT_MIN. (Marc Bennewitz)

- LDAP:
. Fixed ldap_set_option() error message. (Marc Bennewitz)

- Lexbor:
. Merge patches lexbor/lexbor@8a14bc0 and lexbor/lexbor@f67ce4b, fixing a
Expand All @@ -62,6 +70,10 @@ PHP NEWS
. Fixed mbstring functions emitting surrogates in UTF-8 output and flagging
it as valid UTF-8. (Nicolas Grekas)
. Fixed bug GH-23958 (truncated encoding names resolving to full names). (Kamil Tekiela)
. Fixed mbstring.regex_retry_limit above UINT_MAX being ignored.
(Marc Bennewitz)
. Fixed negative mbstring.regex_stack_limit and mbstring.regex_retry_limit
on 32bit platforms. (Marc Bennewitz)

- MySQLi:
. Fix GH-22854: Fixed failed assertion when accessing mysqli property after
Expand Down Expand Up @@ -105,6 +117,8 @@ PHP NEWS
- Phar:
. Fixed bug GH-22556 (OOM DoS via oversized ././@LongLink entry in TAR phar).
(crystarm)
. Fixed Phar::buildFromIterator() accepting a negative getMTime() on 32bit
platforms. (Marc Bennewitz)

- Readline:
. Fixed a heap over-read in the interactive shell prompt when cli.prompt is
Expand Down Expand Up @@ -172,6 +186,10 @@ PHP NEWS
. Fix casing of enum cases in UriHostType and UrlHostType to match the RFC
and PHP’s naming policy. (timwolla)

- Windows:
. Fixed sapi_windows_cp_set() and sapi_windows_cp_conv() accepting a negative
codepage on 32bit platforms. (Marc Bennewitz)

- XML:
. Fixed xml_get_current_byte_index() returning negative values for byte
indexes beyond 2 GiB on Windows x64. (Marc Bennewitz)
Expand Down
7 changes: 7 additions & 0 deletions UPGRADING.INTERNALS
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ PHP 8.7 INTERNALS UPGRADE NOTES
- The INIT_CLASS_ENTRY_INIT_METHODS() macro, which was previously used as
part of handling disabled classes, was removed following the removal of
the `disable_classes` INI setting in PHP 8.5.
- Added ZEND_IN_RANGE_OF(x, T) to check whether the value of an integer x is
representable in the integer type T.
- Added ZEND_TYPE_MIN(T) and ZEND_TYPE_MAX(T).
- Deprecated ZEND_LONG_INT_OVFL(), ZEND_LONG_INT_UDFL(),
ZEND_LONG_EXCEEDS_INT(), ZEND_LONG_UINT_OVFL(), ZEND_SIZE_T_INT_OVFL(),
ZEND_SIZE_T_UINT_OVFL() and the *_CAN_OVFL_* flags. Use ZEND_IN_RANGE_OF()
instead.

========================
2. Build system changes
Expand Down
5 changes: 3 additions & 2 deletions Zend/zend_portability.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,6 @@ using std::atomic_int;
#include <intrin.h>
#endif

#include "zend_range_check.h"

/* GCC x.y.z supplies __GNUC__ = x and __GNUC_MINOR__ = y */
#ifdef __GNUC__
# define ZEND_GCC_VERSION (__GNUC__ * 1000 + __GNUC_MINOR__)
Expand Down Expand Up @@ -947,4 +945,7 @@ static zend_always_inline uint64_t ZEND_BYTES_SWAP64(uint64_t u)
# define ZEND_OPCACHE_SHM_REATTACHMENT 1
#endif

/* Last, as its declarations use ZEND_ATTRIBUTE_DEPRECATED. */
#include "zend_range_check.h"

#endif /* ZEND_PORTABILITY_H */
155 changes: 124 additions & 31 deletions Zend/zend_range_check.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
| SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Anatol Belski <ab@php.net> |
| Marc Bennewitz <marc@mabe.berlin> |
+----------------------------------------------------------------------+
*/

Expand All @@ -20,47 +21,139 @@

#include "zend_long.h"

/* Flag macros for basic range recognition. Notable is that
always sizeof(signed) == sizeof(unsigned), so no need to
overcomplicate things. */
#if SIZEOF_INT < SIZEOF_ZEND_LONG
# define ZEND_LONG_CAN_OVFL_INT 1
# define ZEND_LONG_CAN_OVFL_UINT 1
#endif
/* _ZEND_INT_TYPE_CHECK(T): the integer constant 0,
* or a compile error unless T is an integer type not wider than intmax_t. */
#ifndef __cplusplus
/* C: a static_assert() cannot appear in an expression itself,
* but it can as a member of a struct of which only the size is taken.
* The "% 1" only compiles for an integer operand. */
# include <assert.h>

#if SIZEOF_INT < SIZEOF_SIZE_T
/* size_t can always overflow signed int on the same platform.
Furthermore, by the current design, size_t can always
overflow zend_long. */
# define ZEND_SIZE_T_CAN_OVFL_UINT 1
# define _ZEND_INT_TYPE_CHECK(T) \
((int)(0 * sizeof(struct { \
static_assert( \
sizeof((T)0 % 1) && sizeof(T) <= sizeof(intmax_t), \
"T must be an integer type not wider than intmax_t" \
); \
char zend_dummy; \
})))
#else
/* C++ does not allow to define a struct within sizeof(),
* so the static_assert() lives in a class template instead,
* which taking the size of instantiates. */
extern "C++" {
# include <cstdint>
# include <type_traits>
template<typename T> struct _zend_int_type_check {
static_assert(std::is_integral<T>::value && sizeof(T) <= sizeof(intmax_t),
"T must be an integer type not wider than intmax_t");
};
}

# define _ZEND_INT_TYPE_CHECK(T) ((int)(0 * sizeof(_zend_int_type_check<T>)))
#endif

/* _ZEND_TYPE_IS_SIGNED(T): whether the integer type T is signed, as an integer constant expression. */
#define _ZEND_TYPE_IS_SIGNED(T) (!((T)-1 > (T)0))

/* zend_long vs. (unsigned) int checks. */
#ifdef ZEND_LONG_CAN_OVFL_INT
# define ZEND_LONG_INT_OVFL(zlong) UNEXPECTED((zlong) > (zend_long)INT_MAX)
# define ZEND_LONG_INT_UDFL(zlong) UNEXPECTED((zlong) < (zend_long)INT_MIN)
# define ZEND_LONG_EXCEEDS_INT(zlong) UNEXPECTED(ZEND_LONG_INT_OVFL(zlong) || ZEND_LONG_INT_UDFL(zlong))
# define ZEND_LONG_UINT_OVFL(zlong) UNEXPECTED((zlong) < 0 || (zlong) > (zend_long)UINT_MAX)
#else
# define ZEND_LONG_INT_OVFL(zl) (0)
# define ZEND_LONG_INT_UDFL(zl) (0)
# define ZEND_LONG_EXCEEDS_INT(zlong) (0)
# define ZEND_LONG_UINT_OVFL(zl) (0)
#endif
/* ZEND_TYPE_MAX(T): the largest value of the integer type T, as an integer constant expression,
* for types without a constant of their own such as time_t.
* T may be any signed or unsigned integer type not wider than intmax_t. */
#define ZEND_TYPE_MAX(T) (_ZEND_INT_TYPE_CHECK(T) + (_ZEND_TYPE_IS_SIGNED(T) \
? (((((T)1 << (sizeof(T) * CHAR_BIT - 2)) - 1) * 2) + 1) \
: (T)-1 \
))

/* ZEND_TYPE_MIN(T): the smallest value of the integer type T, as an integer constant expression.
* T may be any signed or unsigned integer type not wider than intmax_t. */
#define ZEND_TYPE_MIN(T) (_ZEND_TYPE_IS_SIGNED(T) ? -ZEND_TYPE_MAX(T) - 1 : (T)0)

/* ZEND_IN_RANGE_OF */
#ifndef __cplusplus
/* C: _Generic dispatches on the promoted type of x, without evaluating it,
* to a comparison done in intmax_t for a signed x or in uintmax_t for an unsigned one,
* against ZEND_TYPE_MIN(T) and ZEND_TYPE_MAX(T). */
static inline bool _zend_in_range_signed(intmax_t v, intmax_t min, uintmax_t max)
{
if (max <= INTMAX_MAX) {
return v >= min && v <= (intmax_t)max;
}
return v >= 0; /* T is a uintmax_t-wide unsigned type */
}

/* size_t vs (unsigned) int checks. */
#define ZEND_SIZE_T_INT_OVFL(size) UNEXPECTED((size) > (size_t)INT_MAX)
#ifdef ZEND_SIZE_T_CAN_OVFL_UINT
# define ZEND_SIZE_T_UINT_OVFL(size) UNEXPECTED((size) > (size_t)UINT_MAX)
static inline bool _zend_in_range_unsigned(uintmax_t v, uintmax_t max)
{
return v <= max;
}

# define _ZEND_IN_RANGE_SIGNED(x, T) _zend_in_range_signed((x), ZEND_TYPE_MIN(T), ZEND_TYPE_MAX(T))
# define _ZEND_IN_RANGE_UNSIGNED(x, T) _zend_in_range_unsigned((x), ZEND_TYPE_MAX(T))

/* ZEND_IN_RANGE_OF(x, T): whether the value of the integer expression x is representable in the integer type T.
* x and T may be any signed or unsigned integer types not wider than intmax_t.
* x is evaluated exactly once. */
# define ZEND_IN_RANGE_OF(x, T) EXPECTED(_Generic((x) + 0, \
int: _ZEND_IN_RANGE_SIGNED(x, T), \
long: _ZEND_IN_RANGE_SIGNED(x, T), \
long long: _ZEND_IN_RANGE_SIGNED(x, T), \
unsigned int: _ZEND_IN_RANGE_UNSIGNED(x, T), \
unsigned long: _ZEND_IN_RANGE_UNSIGNED(x, T), \
unsigned long long: _ZEND_IN_RANGE_UNSIGNED(x, T) \
))
#else
# define ZEND_SIZE_T_UINT_OVFL(size) (0)
/* C++ has no _Generic (g++ and MSVC reject it).
* A function template deduces the type of x instead.
* The unary + applies the integer promotions, as (x) + 0 does for C. */
extern "C++" {
template<typename T, typename X>
constexpr bool _zend_in_range_of(X x) {
static_assert(std::is_integral<X>::value && sizeof(X) <= sizeof(intmax_t), "x must be an integer not wider than intmax_t");

return std::is_signed<X>::value
? (intmax_t)x >= (intmax_t)ZEND_TYPE_MIN(T)
&& ((uintmax_t)ZEND_TYPE_MAX(T) > (uintmax_t)INTMAX_MAX
|| (intmax_t)x <= (intmax_t)ZEND_TYPE_MAX(T))
: (uintmax_t)x <= (uintmax_t)ZEND_TYPE_MAX(T);
}
}

/* ZEND_IN_RANGE_OF(x, T): whether the value of the integer expression x is representable in the integer type T.
* x and T may be any signed or unsigned integer types not wider than intmax_t.
* x is evaluated exactly once. */
# define ZEND_IN_RANGE_OF(x, T) EXPECTED(_zend_in_range_of<T>(+(x)))
#endif

/* Comparison zend_long vs size_t */
#define ZEND_SIZE_T_GT_ZEND_LONG(size, zlong) ((zlong) < 0 || (size) > (size_t)(zlong))
#define ZEND_SIZE_T_GT_ZEND_LONG(size, zlong) ((zlong) < 0 || (size) > (size_t)(zlong))
#define ZEND_SIZE_T_GTE_ZEND_LONG(size, zlong) ((zlong) < 0 || (size) >= (size_t)(zlong))
#define ZEND_SIZE_T_LT_ZEND_LONG(size, zlong) ((zlong) >= 0 && (size) < (size_t)(zlong))
#define ZEND_SIZE_T_LT_ZEND_LONG(size, zlong) ((zlong) >= 0 && (size) < (size_t)(zlong))
#define ZEND_SIZE_T_LTE_ZEND_LONG(size, zlong) ((zlong) >= 0 && (size) <= (size_t)(zlong))

/* -- DEPRECATED -- */

/* The zend_long and size_t vs. (unsigned) int checks below are deprecated, use ZEND_IN_RANGE_OF() instead. */
static inline ZEND_ATTRIBUTE_DEPRECATED void _zend_deprecated_use_ZEND_IN_RANGE_OF_instead(void) {}

#if SIZEOF_INT < SIZEOF_ZEND_LONG
# define ZEND_LONG_CAN_OVFL_INT 1 /* Deprecated */
# define ZEND_LONG_CAN_OVFL_UINT 1 /* Deprecated */
# define ZEND_LONG_INT_OVFL(zlong) (_zend_deprecated_use_ZEND_IN_RANGE_OF_instead(), UNEXPECTED((zlong) > (zend_long)INT_MAX))
# define ZEND_LONG_INT_UDFL(zlong) (_zend_deprecated_use_ZEND_IN_RANGE_OF_instead(), UNEXPECTED((zlong) < (zend_long)INT_MIN))
# define ZEND_LONG_EXCEEDS_INT(zlong) (_zend_deprecated_use_ZEND_IN_RANGE_OF_instead(), UNEXPECTED((zlong) > (zend_long)INT_MAX || (zlong) < (zend_long)INT_MIN))
# define ZEND_LONG_UINT_OVFL(zlong) (_zend_deprecated_use_ZEND_IN_RANGE_OF_instead(), UNEXPECTED((zlong) < 0 || (zlong) > (zend_long)UINT_MAX))
#else
# define ZEND_LONG_INT_OVFL(zl) (_zend_deprecated_use_ZEND_IN_RANGE_OF_instead(), 0)
# define ZEND_LONG_INT_UDFL(zl) (_zend_deprecated_use_ZEND_IN_RANGE_OF_instead(), 0)
# define ZEND_LONG_EXCEEDS_INT(zlong) (_zend_deprecated_use_ZEND_IN_RANGE_OF_instead(), 0)
# define ZEND_LONG_UINT_OVFL(zl) (_zend_deprecated_use_ZEND_IN_RANGE_OF_instead(), 0)
#endif

#define ZEND_SIZE_T_INT_OVFL(size) (_zend_deprecated_use_ZEND_IN_RANGE_OF_instead(), UNEXPECTED((size) > (size_t)INT_MAX))
#if SIZEOF_INT < SIZEOF_SIZE_T
# define ZEND_SIZE_T_CAN_OVFL_UINT 1 /* Deprecated */
# define ZEND_SIZE_T_UINT_OVFL(size) (_zend_deprecated_use_ZEND_IN_RANGE_OF_instead(), UNEXPECTED((size) > (size_t)UINT_MAX))
#else
# define ZEND_SIZE_T_UINT_OVFL(size) (_zend_deprecated_use_ZEND_IN_RANGE_OF_instead(), 0)
#endif

#endif /* ZEND_RANGE_CHECK_H */
2 changes: 1 addition & 1 deletion ext/bcmath/bcmath.c
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ static zend_always_inline zend_result bcmath_check_scale(zend_long scale, uint32

static zend_result bcmath_check_precision(zend_long precision, uint32_t arg_num)
{
if (ZEND_LONG_INT_OVFL(precision)) {
if (precision > 0 && !ZEND_IN_RANGE_OF(precision, int)) {
zend_argument_value_error(arg_num, "must be between " ZEND_LONG_FMT " and %d",
(zend_long) ZEND_LONG_MIN, INT_MAX);
return FAILURE;
Expand Down
4 changes: 2 additions & 2 deletions ext/calendar/calendar.c
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ PHP_FUNCTION(cal_to_jd)
RETURN_THROWS();
}

if (UNEXPECTED(ZEND_LONG_EXCEEDS_INT(day))) {
if (!ZEND_IN_RANGE_OF(day, int32_t)) {
zend_argument_value_error(3, "must be between %d and %d", INT32_MIN, INT32_MAX);
RETURN_THROWS();
}
Expand Down Expand Up @@ -516,7 +516,7 @@ PHP_FUNCTION(jewishtojd)
RETURN_THROWS();
}

if (ZEND_LONG_EXCEEDS_INT(year)) {
if (!ZEND_IN_RANGE_OF(year, int)) {
zend_argument_value_error(3, "must be between %d and %d", INT_MIN, INT_MAX);
RETURN_THROWS();
}
Expand Down
4 changes: 2 additions & 2 deletions ext/curl/interface.c
Original file line number Diff line number Diff line change
Expand Up @@ -3054,7 +3054,7 @@ PHP_FUNCTION(curl_escape)

ch = Z_CURL_P(zid);

if (ZEND_SIZE_T_INT_OVFL(ZSTR_LEN(str))) {
if (!ZEND_IN_RANGE_OF(ZSTR_LEN(str), int)) {
RETURN_FALSE;
}

Expand Down Expand Up @@ -3083,7 +3083,7 @@ PHP_FUNCTION(curl_unescape)

ch = Z_CURL_P(zid);

if (ZEND_SIZE_T_INT_OVFL(ZSTR_LEN(str))) {
if (!ZEND_IN_RANGE_OF(ZSTR_LEN(str), int)) {
RETURN_FALSE;
}

Expand Down
2 changes: 1 addition & 1 deletion ext/dba/dba.c
Original file line number Diff line number Diff line change
Expand Up @@ -1057,7 +1057,7 @@ PHP_FUNCTION(dba_fetch)
ZEND_PARSE_PARAMETERS_END();
}

if (ZEND_LONG_EXCEEDS_INT(skip)) {
if (!ZEND_IN_RANGE_OF(skip, int)) {
zend_argument_value_error(3, "must be between %d and %d", INT_MIN, INT_MAX);
RETURN_THROWS();
}
Expand Down
8 changes: 4 additions & 4 deletions ext/dom/characterdata.c
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ PHP_METHOD(DOMCharacterData, substringData)
const xmlChar *cur = php_dom_get_content_or_empty(node);

length = xmlUTF8Strlen(cur);
if (ZEND_LONG_INT_OVFL(offset_input) || ZEND_LONG_INT_OVFL(count_input)) {
if ((offset_input > 0 && !ZEND_IN_RANGE_OF(offset_input, int)) || (count_input > 0 && !ZEND_IN_RANGE_OF(count_input, int))) {
php_dom_throw_error(INDEX_SIZE_ERR, dom_get_strict_error(intern->document));
RETURN_FALSE;
}
Expand Down Expand Up @@ -206,7 +206,7 @@ static void dom_character_data_insert_data(INTERNAL_FUNCTION_PARAMETERS, bool re

length = xmlUTF8Strlen(cur);

if (ZEND_LONG_INT_OVFL(offset_input)) {
if (offset_input > 0 && !ZEND_IN_RANGE_OF(offset_input, int)) {
php_dom_throw_error(INDEX_SIZE_ERR, dom_get_strict_error(intern->document));
RETURN_FALSE;
}
Expand Down Expand Up @@ -271,7 +271,7 @@ static void dom_character_data_delete_data(INTERNAL_FUNCTION_PARAMETERS, bool re

length = xmlUTF8Strlen(cur);

if (offset < 0 || ZEND_LONG_INT_OVFL(offset) || ZEND_LONG_INT_OVFL(count_input) || offset > length) {
if (offset < 0 || !ZEND_IN_RANGE_OF(offset, int) || (count_input > 0 && !ZEND_IN_RANGE_OF(count_input, int)) || offset > length) {
php_dom_throw_error(INDEX_SIZE_ERR, dom_get_strict_error(intern->document));
RETURN_FALSE;
}
Expand Down Expand Up @@ -341,7 +341,7 @@ static void dom_character_data_replace_data(INTERNAL_FUNCTION_PARAMETERS, bool r

length = xmlUTF8Strlen(cur);

if (offset < 0 || ZEND_LONG_INT_OVFL(offset) || ZEND_LONG_INT_OVFL(count_input) || offset > length) {
if (offset < 0 || !ZEND_IN_RANGE_OF(offset, int) || (count_input > 0 && !ZEND_IN_RANGE_OF(count_input, int)) || offset > length) {
php_dom_throw_error(INDEX_SIZE_ERR, dom_get_strict_error(intern->document));
RETURN_FALSE;
}
Expand Down
Loading
Loading