- 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>
This is unused; this is a vestige of 6c158374ba
and 45e327a672, which set up some timeout code
which was removed in 650c1c0a7d. Remove this
useless function from SAPIs.
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.
PHPDBG_G(watch_recreation) is keyed by element->str, so a watch element and
its implicit parent are stored under distinct keys ("$lower[0]" and
"$lower[]"). The deduplication in phpdbg_queue_element_for_recreation() only
walks down from the entry found under the *same* key, so it never notices
that the two belong to the same chain and both get queued.
phpdbg_free_watch_element_tree() frees the element together with its entire
parent and child chains, so draining watch_recreation freed that chain twice:
the first entry frees the whole chain, the second one then walks the already
freed links and frees them again.
Drop every entry referencing a member of the chain before freeing it. The
buckets are nulled out rather than deleted, as all callers are iterating over
the hash at that point; they clean it right afterwards.
Continues #22565 for four sites that were missed: _class_exists_impl(),
function_exists(), pdo_stmt.c's dbstmt_method_get(), ReflectionClass::
hasMethod()/getMethod(), and phpdbg's print func/method commands. No
behavior change.
phpdbg watched every array element as a Bucket, but packed arrays store
bare zvals and de-indirected stack variables aren't buckets, so reading
Bucket.h/.key over-read the neighbour and a sibling write tripped a
phantom break. Watch such elements as WATCH_ON_ZVAL and compare the type
info first, reading the value union only for initialised slots so an
uninitialised CV is never compared by value.
A separate pre-existing bug crashed 32-bit: phpdbg_btree_insert_or_update
linked each new node into the tree before initialising its child
pointers, so a write to a node on a watched page faulted into the
watchpoint handler, which walked the half-built node. Build the node
fully, then publish it with a single store.
Closes GH-22756
phpdbg_add_watch_element() frees the passed element and returns the
existing one when the variable is already watched, but several callers
kept using the freed pointer, so re-watching a variable was a
use-after-free in more places than the two reported. Report the duplicate
back through the parse callback and register nothing, and reject a
recursive watch's hashtable element that collides with an existing watch,
at creation and recreation, rather than freeing a still-referenced one. A
recursive watch over an already-array-watched variable now watches only
the root and skips the shared hashtable.
Running the tests under ASAN exposed a second, latent use-after-free at
shutdown: the watch tables are module-scoped but hold request-pool memory,
so under the tracked allocator freeing a watched variable during executor
teardown re-queues an element after the last drain, tracked_free_all frees
the pool, and phpdbg_destroy_watchpoints reads it at module shutdown. Tear
the watch state down in a post_deactivate handler, which runs after the
executor frees values but before the memory manager is torn down.
Dropping watch_006's xfail surfaced a separate pre-existing bug on
large-page platforms: the fault handler rechecks every watchpoint on the
faulting page, so on 16 KB pages a value-only write to an element can
fault the page holding a watched hashtable's own struct and run the
recursive rescan, which re-reported existing elements as freshly added.
Only run that rescan when the element count actually changed.
Fixes GH-22480
The switch in phpdbg_print_breakpoints() assigned str_type per case
and jumped to a shared label that immediately recomputed the same
value from brake->type, making the pre-goto assignments dead. Set
str_type directly per case instead and drop the goto.
lc_length was set to name_length + 1, one past the actual lowercased
string length, so zend_hash_str_find_ptr() (which expects a strlen-style
length) never matched an existing class entry. This made class lookups
during phpdbg's signal-handler interruption path always fail silently.
Replaced the manual emalloc/tolower/efree dance with
zend_hash_str_find_ptr_lc(), which performs the lowercase copy and
lookup in one call and removes the surface for this class of bug.
This removes leftover win32/param.h / <sys/param.h> includes from files that
do not use any of the macros provided by those headers (MAXPATHLEN,
MAXHOSTNAMELEN, howmany, roundup).
It also removes local Windows O_RDONLY -> _O_RDONLY aliases from files where
O_RDONLY is not used.
phpdbg_resolve_opline_break() passed the result of zend_str_tolower_dup()
directly as the lookup key to zend_hash_str_find_ptr() for both the class
and the function/method lookups. That emalloc'd buffer was never freed,
leaking on every method/function opline breakpoint resolution.
Use zend_hash_str_find_ptr_lc(), which lowercases into a (stack-allocated
for short names) temporary and frees it internally, instead of duplicating
the key by hand and leaking it.
Add a regression test that exercises all three paths through the two
lookups (class + method found, method not found, class not found).
Closes GH-22563
A number of call sites lowercased a class/function/method name into a
temporary string solely to use it as a lookup key in the class table,
function table, module registry, etc., then released it. Replace those
with zend_hash_find_ptr_lc() / zend_hash_str_find_ptr_lc(), which
lowercase into a stack buffer (for short names), perform the lookup, and
free the temporary internally.
This removes the manual tolower + release boilerplate at 17 sites and
avoids a heap allocation for the common short-name case. No behavior
change.
In the NORMAL condition a lone unterminated quote matched no rule, so
re2c backtracked to a zero-length GENERIC_ID accept. The rule body then
computed yyleng - unescape_string(yytext) as 0 - N, underflowing size_t
into a multi-gigabyte estrndup. Give a bare quote an explicit
one-character accept so the cursor advances and unescape_string is never
reached with a malformed token.
Fixes GH-17387
Closes GH-22506
There are several issues with this macro:
- It is incorrectly named: It's not an empty default case. It's an unreachable
default case.
- It is hiding control flow in its definition, which can be confusing for
humans and tools (such as Coccinelle) alike, because it looks like it would
be a statement that belongs to the "current" case rather than opening a new
one.
Since this macro is exactly as long as explicitly spelling out its definition
(excluding the useless `break;`), it is not even making the code any more
succinct.
Accept a zend_string* to be able to use the zend_string_concat APIs
Make the function static as it's not exported in a header and only used inside this file
Audit zend_ini_string(), INI_STR(), and related functions and macros, and all other INI_* macros.
The primary motivation is that these APIs should return a `const char*` because the `char*` is owned by a zend_string, and thus should never be released.
Moreover, the INI_STR() API doesn't follow our usual naming scheme as it returns a char* rather than a zend_string*.
In this PR new zend_ini_{bool|long|double|str|string}_literal() macros are introduced replacing the INI_{BOOL|INT|FLT|STR} macros which follow our more modern naming convention.
Moreover, the INI_BOOL() macro didn't produce correct values if the INI string is a word like "true" or "on".
The INI_ORIG_* APIs are removed because a Sourcegraph search shows 0 results, other than the one case used in ext/tidy that we fix using the typical API.
Add some additional checks for the value of an INI string to ensure it doesn't contain a nul byte when describing a path.