- 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>
Set close-on-exec atomically when opening the session file instead of
issuing a separate fcntl(F_SETFD) call afterwards. This saves one
syscall per request and closes the window in which the descriptor could
leak into a child process forked by another thread. The fcntl() fallback
is kept for platforms without O_CLOEXEC.
Truncating before a shorter write discards the old session data even when
the write fails without storing any bytes. Truncate only after the full
write and update the cached size for subsequent writes.
Closes GH-23653
Drop the pre-emptive rmdir() before mkdir(), move cleanup into --CLEAN--
sections, stop bug61470 from writing session files into the tests
directory, and remove a --CLEAN-- section in rfc1867_sid_invalid that
deleted a file nothing creates.
session_start_cookie_header_rejected.phpt injected a newline through
session.cookie_path to make the SAPI reject the Set-Cookie header.
Since b9a0228bd8 session.cookie_path and session.cookie_domain reject
CR/LF up front, so no userland input can reach that branch any more
and the test failed.
php_session_send_cookie() appends session.cookie_path and
session.cookie_domain to the Set-Cookie header verbatim, and neither
setting rejected the separators that terminate a cookie attribute. An
application could therefore append its own Domain, Path, HttpOnly,
Secure, or SameSite attribute through either value. cookie_path now
rejects ";" and CR/LF, which RFC 6265 path-value excludes, while keeping
the comma it allows; cookie_domain takes the set setcookie() enforces on
$domain.
Closes GH-22925
php_session_reset() called php_session_initialize() on an already active
session, so the save handler's open() and read() ran a second time with no
intervening close(). Abort the session first, pairing every open() with a
close().
Closes GH-23597
session_regenerate_id() runs the userland write or destroy handler and
then the close handler, and keeps operating on PS(id) afterwards. Any of
them may call session_destroy(): php_session_destroy() runs
php_rshutdown_session_globals() even when the recursive call was
rejected, releasing PS(id) and setting it to NULL, so the following
zend_string_release_ex() dereferences NULL. Check that the session is
still active once every handler has run.
Closes GH-22926
Since 86b4921157, php_session_save_current_state() returns without calling
the save handler's close callback when the serializer returns NULL, e.g. for
a $_SESSION key containing '|'. session_write_close() still returns true, but
the files handler keeps its LOCK_EX on the session file until request
shutdown, blocking concurrent requests for the same session, and a user
handler extending SessionHandler has its close() run at shutdown after the
session is gone. Close the handler on that return as the rest of the function
and session_regenerate_id() do; with an exception pending the engine already
refuses to call a user close() callback.
Closes GH-24022
Correct typographical and grammatical mistakes across docs, comments, and build tools. Refactor local identifiers childrens to children in RecursiveFilterIterator and sav to saved_char in sapi_header_add_op.
Do not replace pending exceptions with return-value validation errors in
the user handler adapter, including exceptions raised while destroying
invalid return values. This preserves the original exception for
session_start(), session_create_id(), and session_regenerate_id().
Closes GH-23854
When a save handler rejects the session ID under session.use_strict_mode,
php_session_initialize() papered over a failing s_create_sid() by generating
an internal ID and carrying on, so reset_id()'s Set-Cookie and SID side
effects ran for a session that never started, and a second failure left
PS(id) NULL. Mirror the primary no-ID branch: abort the session, throw when
no exception is pending, and return FAILURE. Sibling sites audited,
session_regenerate_id() and session_create_id() already NULL-check with
their own error paths.
Closes GH-23847
commit 33b13ae5de
Author: Michael Orlitzky <michael@orlitzky.com>
Date: Tue Sep 1 17:58:13 2026 -0400
ext/session/.../gh23043.phpt: session.save_path can be nonempty
Allow session.save_path to be nonempty in this test, as the test
itself does not empty it, and in automated setups it is common to
force all "temporary stuff" to a dedicated location.
Closes GH-23537
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.
php_session_send_cookie() discarded the result of sapi_add_header_ex()
and always returned SUCCESS. When the SAPI refuses a Set-Cookie header
carrying CR or LF, the warning it emits can reach a userland error
handler that calls session_destroy(), and php_session_reset_id() then
appends the released PS(id). Return what sapi_add_header_ex() reports so
the caller stops before touching session state again.
Closes GH-22923
The method landed in 6901c87aea with no NEWS or UPGRADING entry. Add
both, and a test pinning that an unknown ID is regenerated rather than
adopted when the built-in handler runs with session.use_strict_mode=1.
Closes GH-23071
s_create_sid() can return NULL when php_random_bytes_throw() fails
(e.g. CSPRNG exhaustion), but RETURN_STR() dereferences the string
unconditionally. Every other internal caller of s_create_sid() in
session.c (php_session_initialize, session_regenerate_id) already
NULL-checks the result; this PHP-facing method, reachable from any
userland SessionHandler subclass via create_sid(), did not.
No dedicated regression test is added: forcing php_random_bytes_throw()
to fail is not portably reproducible from a .phpt test (it's a raw
getrandom() syscall on Linux and CCRandomGenerateBytes on macOS, neither
of which can be faulted from userland), which is also why the existing
NULL-checks this mirrors in session.c have none either.
We *explicitly* say that mod_data will be not NULL for create_sid hooks and we violate this invariant if SessionHandler::open() is overwridden without calling the parent method.
At the same time add comments explaining the situation and fix some other potential footguns.