From 291725200d4e351c1ea3117b5b79e1d6a34c92f7 Mon Sep 17 00:00:00 2001 From: Taylor Christian Newsome <86472964+SleepTheGod@users.noreply.github.com> Date: Mon, 26 Oct 2026 23:27:16 -0400 Subject: [PATCH] Fix CVE-2026-109619 and improve session error handling Added handling for CVE-2026-109619 to clear request-scoped rfc1867 state and prevent stale pointers/flags from persisting across requests. Implemented error handling for out-of-memory conditions during session initialization and cleanup. --- ext/session/session.c | 87 ++++++++++++++++++++++++++++++++----------- 1 file changed, 65 insertions(+), 22 deletions(-) diff --git a/ext/session/session.c b/ext/session/session.c index 12c4ae57fdcc..878124751326 100644 --- a/ext/session/session.c +++ b/ext/session/session.c @@ -127,6 +127,7 @@ static inline void php_rinit_session_globals(void) PS(session_vars) = NULL; PS(module_number) = my_module_number; PS(in_rfc1867_update) = false; + PS(rfc1867_progress) = NULL; ZVAL_UNDEF(&PS(http_session_vars)); } @@ -185,6 +186,12 @@ static void php_rshutdown_session_globals(void) php_session_cleanup_filename(); + /* CVE-2026-109619: clear request-scoped rfc1867 state so a longjmp() + * during pre-RINIT processing can never leave stale pointers/flags + * visible to the next request on the same worker. */ + PS(in_rfc1867_update) = false; + PS(rfc1867_progress) = NULL; + /* User save handlers may end up directly here by misuse, bugs in user script, etc. */ /* Set session status to prevent error while restoring save handler INI value. */ PS(session_status) = php_session_none; @@ -260,6 +267,10 @@ static void php_session_track_init(void) if (!Z_ISUNDEF(PS(http_session_vars))) { zval_ptr_dtor(&PS(http_session_vars)); + /* CVE-2026-109619: clear the slot immediately so an OOM/longjmp() + * during array_init() below cannot leave a dangling pointer that + * rshutdown would double-free. */ + ZVAL_UNDEF(&PS(http_session_vars)); } array_init(&session_vars); @@ -507,7 +518,10 @@ static zend_result php_session_initialize(void) if (PS(lazy_write)) { PS(session_vars) = zend_string_copy(val); } - php_session_decode(val); + if (php_session_decode(val) == FAILURE) { + zend_string_release_ex(val, false); + return FAILURE; + } zend_string_release_ex(val, false); } @@ -2985,6 +2999,7 @@ static PHP_GINIT_FUNCTION(ps) ps_globals->serializer = NULL; ps_globals->mod_data = NULL; ps_globals->in_rfc1867_update = false; + ps_globals->rfc1867_progress = NULL; ps_globals->session_status = php_session_none; ps_globals->default_mod = NULL; ps_globals->mod_user_implemented = false; @@ -3217,35 +3232,48 @@ static void php_session_rfc1867_update(php_session_rfc1867_progress *progress, b /* Runs before RINIT: no user code may be invoked, see php_session_unserialize_init() */ PS(in_rfc1867_update) = true; - if (php_session_initialize() == FAILURE || PS(session_status) != php_session_active) { - PS(in_rfc1867_update) = false; - return; - } - IF_SESSION_VARS() { - zval *sess_var = Z_REFVAL(PS(http_session_vars)); - SEPARATE_ARRAY(sess_var); + zend_try { + if (php_session_initialize() == FAILURE || PS(session_status) != php_session_active) { + PS(in_rfc1867_update) = false; + return; + } + IF_SESSION_VARS() { + zval *sess_var = Z_REFVAL(PS(http_session_vars)); + SEPARATE_ARRAY(sess_var); - progress->cancel_upload |= php_check_cancel_upload(progress); - Z_TRY_ADDREF(progress->data); - zend_hash_update(Z_ARRVAL_P(sess_var), progress->key.s, &progress->data); - } - php_session_flush(true); + progress->cancel_upload |= php_check_cancel_upload(progress); + Z_TRY_ADDREF(progress->data); + zend_hash_update(Z_ARRVAL_P(sess_var), progress->key.s, &progress->data); + } + php_session_flush(true); + } zend_catch { + /* CVE-2026-109619: on OOM/fatal bailout, clear the pre-RINIT + * deserialization guard before unwinding so it can never leak into + * shutdown or the next request on this worker. */ + PS(in_rfc1867_update) = false; + zend_bailout(); + } zend_end_try(); PS(in_rfc1867_update) = false; } static void php_session_rfc1867_cleanup(const php_session_rfc1867_progress *progress) { PS(in_rfc1867_update) = true; - if (php_session_initialize() == FAILURE || PS(session_status) != php_session_active) { + zend_try { + if (php_session_initialize() == FAILURE || PS(session_status) != php_session_active) { + PS(in_rfc1867_update) = false; + return; + } + IF_SESSION_VARS() { + zval *sess_var = Z_REFVAL(PS(http_session_vars)); + SEPARATE_ARRAY(sess_var); + zend_hash_del(Z_ARRVAL_P(sess_var), progress->key.s); + } + php_session_flush(true); + } zend_catch { PS(in_rfc1867_update) = false; - return; - } - IF_SESSION_VARS() { - zval *sess_var = Z_REFVAL(PS(http_session_vars)); - SEPARATE_ARRAY(sess_var); - zend_hash_del(Z_ARRVAL_P(sess_var), progress->key.s); - } - php_session_flush(true); + zend_bailout(); + } zend_end_try(); PS(in_rfc1867_update) = false; } @@ -3283,6 +3311,9 @@ static zend_result php_session_rfc1867_callback(unsigned int event, void *event_ const multipart_event_formdata *data = event_data; size_t value_len; + if (progress == NULL) { + break; + } if (Z_TYPE(progress->sid) && progress->key.s) { break; } @@ -3315,6 +3346,9 @@ static zend_result php_session_rfc1867_callback(unsigned int event, void *event_ case MULTIPART_EVENT_FILE_START: { const multipart_event_file_start *data = event_data; + if (progress == NULL) { + break; + } /* Do nothing when $_POST["PHP_SESSION_UPLOAD_PROGRESS"] is not set * or when we have no session id */ if (!Z_TYPE(progress->sid) || !progress->key.s) { @@ -3376,6 +3410,9 @@ static zend_result php_session_rfc1867_callback(unsigned int event, void *event_ case MULTIPART_EVENT_FILE_DATA: { const multipart_event_file_data *data = event_data; + if (progress == NULL) { + break; + } if (!Z_TYPE(progress->sid) || !progress->key.s) { break; } @@ -3389,6 +3426,9 @@ static zend_result php_session_rfc1867_callback(unsigned int event, void *event_ case MULTIPART_EVENT_FILE_END: { const multipart_event_file_end *data = event_data; + if (progress == NULL) { + break; + } if (!Z_TYPE(progress->sid) || !progress->key.s) { break; } @@ -3408,6 +3448,9 @@ static zend_result php_session_rfc1867_callback(unsigned int event, void *event_ case MULTIPART_EVENT_END: { const multipart_event_end *data = event_data; + if (progress == NULL) { + break; + } if (Z_TYPE(progress->sid) && progress->key.s) { if (PS(rfc1867_cleanup)) { php_session_rfc1867_cleanup(progress);