- Replace bailout on OOM with UnwindExit
- Properly reset various globals that may be accessed during startup
Fix GHSA-w8hw-w9fx-j79w (CVE-2026-109618)
Fix GHSA-6wmf-4rv5-p3rp (CVE-2026-109619)
Co-authored-by: Ilija Tovilo <ilija.tovilo@me.com>
Co-authored-by: Jakub Zelenka <bukka@php.net>
Co-authored-by: Nora Dossche <7771979+ndossche@users.noreply.github.com>
Co-authored-by: Nicolas Grekas <nicolas.grekas@gmail.com>
The fix for https://bugs.php.net/bug.php?id=81481 made the libxml
compatibility wrapper return long, which is still 32-bit on Windows x64,
so byte indexes beyond 2 GiB turned negative there. Return zend_long
instead and run the test on Windows too.
Closes GH-23901.
Backport PR #23818 from master to PHP-8.4.
Original commit: 522b1be810
The SAX handler setters store their callback as a zend_fcall_info_cache on
the parser and xml_set_handler() releases the previous one with
zend_fcc_dtor(). None of the setters checked parser->isparsing, so calling
xml_set_element_handler() (or any xml_set_*_handler()) from inside a running
handler freed the fcc that was still executing. For a non-closure
[$object, 'method'] handler whose object is only referenced by that fcc, the
zend_fcc_dtor() releases the last reference and frees $this while its method
is still on the stack, leading to a use-after-free.
Guard xml_set_element_handler() and the XML_SET_HANDLER_PHP_FUNCTION setters
with the existing isparsing flag and throw "Cannot change handlers while
parsing", mirroring the checks already used by xml_parser_set_option() and
xml_parse(). The parsed fcc is released on the error path like the other
failure branches.
Closes#23818
The SAX handler setters store their callback as a zend_fcall_info_cache on
the parser and xml_set_handler() releases the previous one with
zend_fcc_dtor(). None of the setters checked parser->isparsing, so calling
xml_set_element_handler() (or any xml_set_*_handler()) from inside a running
handler freed the fcc that was still executing. For a non-closure
[$object, 'method'] handler whose object is only referenced by that fcc, the
zend_fcc_dtor() releases the last reference and frees $this while its method
is still on the stack, leading to a use-after-free.
Guard xml_set_element_handler() and the XML_SET_HANDLER_PHP_FUNCTION setters
with the existing isparsing flag and throw "Cannot change handlers while
parsing", mirroring the checks already used by xml_parser_set_option() and
xml_parse(). The parsed fcc is released on the error path like the other
failure branches.
Headers required by the generated code (zend_attributes.h, zend_constants.h, zend_enum.h) are collected during generation and emitted as includes at the top of the arginfo file, with the same preprocessor conditions and PHP version guards as the code that needs them.
Removes the manual zend_attributes.h and zend_enum.h includes from sources that only needed them for the arginfo file they include.
* ext/*: Make `X_from_obj(zend_object *obj)` functions macros
With the introduction of the `ZEND_CONTAINER_OF()` macro we can now preserve
whether or not the `zend_object *` is `const` when extracting the
class-specific struct. This change will allow to use `const zend_object *` in
more locations.
Changes performed with Coccinelle followed by some manual cleanup for comment
and whitespace formatting:
@a@
attribute name zend_always_inline;
type T;
identifier obj;
identifier f;
typedef zend_object;
@@
T* f(zend_object *obj) { return ZEND_CONTAINER_OF(obj, T, std); }
+ #define f(obj) ZEND_CONTAINER_OF(obj, T, std)
@@
identifier a.f;
@@
- f(...) { ... }
@b@
attribute name zend_always_inline;
type T;
identifier obj;
identifier f;
typedef zend_object;
@@
T* f(zend_object *obj) { return ZEND_CONTAINER_OF(obj, T, zo); }
+ #define f(obj) ZEND_CONTAINER_OF(obj, T, zo)
@@
identifier b.f;
@@
- f(...) { ... }
* sockets: Fix build
* odbc: Fix build
* zend_portability: Add `ZEND_CONTAINER_OF()`
* tree-wide: Use `ZEND_CONTAINER_OF()`
Changes made with Coccinelle:
@@
type T_container;
identifier member;
expression e;
@@
- (T_container *)(((char *)(e)) - offsetof(T_container, member))
+ ZEND_CONTAINER_OF(e, T_container, member)
@@
type T_container;
identifier member;
expression e;
typedef uintptr_t;
@@
- (T_container *)(((uintptr_t)(e)) - offsetof(T_container, member))
+ ZEND_CONTAINER_OF(e, T_container, member)
@@
type T_container;
identifier member;
expression e;
@@
- (const T_container *)(((char *)(e)) - offsetof(T_container, member))
+ ZEND_CONTAINER_OF(e, T_container, member)
@@
type T_container;
identifier member;
expression e;
typedef uintptr_t;
@@
- (const T_container *)(((uintptr_t)(e)) - offsetof(T_container, member))
+ ZEND_CONTAINER_OF(e, T_container, member)
* bcmath: Fix `get_bcmath_number_from_obj()` const-correctness
A follow-up change to the new `ZEND_CONTAINER_OF()` macro will preserve the
`const`-ness of the input pointer. By wrapping this macro inside a macro rather
an an inline function we can preserve the `const`-ness across all layers.
* zend_portability: Keep `const`-ness in `ZEND_CONTAINER_OF()`
* zend_portability: Add C++ version to `ZEND_CONTAINER_OF()`
The previous code computed `Z_STRLEN_P(myval) + ZSTR_LEN(decoded_value)`
as a plain `size_t` addition before passing the result to
zend_string_extend(), which can wrap on 32-bit and lead to a heap
overflow in the following strncpy(). Switch to zend_string_safe_realloc()
so the size computation is bounds-checked.
close GH-22056
The current function `CHECK_HEADER_ADD_INCLUDE()` automatically defines
`HAVE_<HEADER_NAME_H>` preprocessor macros, which makes it difficult to
sync with other build systems. Specially, if some `HAVE_` macro is used
in the code and this function defines this macro but Autotools doesn't.
The new `CHECK_HEADER()` function behaves similar except it doesn't
define the `HAVE_<HEADER_NAME_H>` preprocessor macro.
This removes the following unused compile definitions:
HAVE_ARGON2_H
HAVE_AVIF_H
HAVE_BZLIB_H
HAVE_CAPSTONE_CAPSTONE_H
HAVE_CURL_EASY_H
HAVE_DB_H
HAVE_DECODE_H
HAVE_DEPOT_H
HAVE_EDITLINE_READLINE_H
HAVE_ENCHANT_H
HAVE_ENCODE_H
HAVE_FFI_H
HAVE_FIREBIRD_INTERFACE_H
HAVE_FT2BUILD_H
HAVE_GD_H
HAVE_GLIB_H
HAVE_GMP_H
HAVE_HTTPD_H
HAVE_IBASE_H
HAVE_IR_IR_H
HAVE_KECCAKHASH_H
HAVE_LBER_H
HAVE_LDAP_H
HAVE_LIBEXSLT_EXSLT_H
HAVE_LIBINTL_H
HAVE_LIBPQ_FE_H
HAVE_LIBTIDY_TIDY_H
HAVE_LIBXML_PARSER_H
HAVE_LIBXML_TREE_H
HAVE_LIBXML_XMLWRITER_H
HAVE_LIBXSLT_XSLT_H
HAVE_LMDB_H
HAVE_MBSTRING_H
HAVE_MYSQL_H
HAVE_ONIGURUMA_H
HAVE_OPENSSL_SSL_H
HAVE_PNG_H
HAVE_SNMP_H
HAVE_SODIUM_H
HAVE_SQLITE3_H
HAVE_SQLITE3EXT_H
HAVE_SYBFRONT_H
HAVE_TIDY_H
HAVE_TIDY_TIDY_H
HAVE_TIDYBUFFIO_H
HAVE_TIMELIB_CONFIG_H
HAVE_UNICODE_USPOOF_H
HAVE_UNICODE_UTF_H
HAVE_XPM_H
HAVE_ZIP_H
HAVE_ZIPCONF_H
HAVE_ZLIB_H
The following compile definitions are defined explicitly:
- HAVE_ICONV_H
- HAVE_MSCOREE_H
- HAVE_SQL_H
- HAVE_SQLEXT_H
Additionally, the `SETUP_OPENSSL()` function doesn't accept the 6th
argument anymore.
Fortunately, libxml won't allow _at this point in time_ to have more
than INT_MAX/5 attributes, so this doesn't cause issues right now.
However, if this limit is ever raised then it can cause an integer
overflow which will cause a heap overflow.
So future-proof this code by properly using safe_emalloc().
Closes GH-20472.
We would need to escape the attributes, but there's no builtin method
that we can call in libxml2 to do so in a way consistent with the
attribute escape rules and expat.
In fact, expat just repeats the input, while we reconstruct it.
To fix the issue, and fix consistency with expat, we repeat the input as
well. This works by seeking to the start and end of the tag and passing
it to the default handler. This is fine for the parser because the
parser used in ext/xml is always in non-progressive mode, so we have
access to the entire input buffer.